3

なぜstd::functionは 2 引数の関数しか認識しないのだろうか。うまく機能するコードをいくつか書きましたが、いくつかの制限があります。どんなフィードバックでも大歓迎です。特に、車輪の再発明をしているのではないかと思います。

私のコードはideoneにあり、参照します。

たとえば、次のように型を記述できますmain

function_type_deducer(main).describe_me();
// Output: I return i and I take 2 arguments.  They are of type:  i PPc

(ここで、'i' は 'int' を意味し、'PPc' はポインタからポインタへの文字を意味します)

Standardstd::functionは 3 つ以上の引数を持つ関数では動作しませんが (私のコードの最後の 2 行を参照)、このコードでは動作します (サンプル コードは 3 つの引数の関数を示しています)。代わりに、私のデザインを標準ライブラリで使用する必要があります! typedef tuple<Args...> args_as_tuple;最初の 2 つの引数タイプだけでなく、すべての引数を格納するように定義します。

主なトリックは、この関数の演繹です。

template<class T, class... Args>
auto function_type_deducer(T(Args...)) -> Function__<T, Args...> {
        return Function__<T, Args...> {};
}

制限:

  • ラムダでは機能しません。これはコンパイルされませんfunction_type_deducer([](){}).describe_me();
  • xyの間に小さな違いがあることにy気づきませstring&ん。(std::function もこれに気づきません)xstring

これらのいずれかを修正する方法についてのアイデアはありますか? 私は車輪を再発明しましたか?

4

2 に答える 2

5

これはコンパイルされませんfunction_type_deducer([](){}).describe_me();

function_type_deducerテンプレじゃなくても大丈夫。:) 非キャプチャ ラムダ (空[]) は暗黙的に関数ポインターに変換可能です。残念ながら、一部のテンプレート引数推定では、暗黙的な変換が考慮されていません。詳細については、この質問を参照してください (コメントが示すように、私の答えが完全に正しいわけではないことに注意してください)。


y は文字列 & を取り、x は文字列を取るため、x と y の間に小さな違いがあることに気付きません。

typeidこの単純なテストコードが示すように、これは関数の問題ではなく、 の問題です。

template<class T>
void x(void(T)){
    T v;
    (void)v;
}

void f1(int){}
void f2(int&){}

int main(){
    x(f1);
    x(f2);
}

Ideone での実例。出力:

エラー: 'v' は参照として宣言されていますが、初期化されていません

簡単な修正は、タグのディスパッチを使用することです。

#include <type_traits> // is_reference
#include <iostream>
#include <typeinfo>

template<class T>
void print_name(std::true_type){
  std::cout << "reference to " << typeid(T).name();
}

template<class T>
void print_name(std::false_type){
  std::cout << typeid(T).name();
}

template<class T>
void print_name(){
  print_name(typename std::is_reference<T>::type());
}

print_name<NextArg>()の代わりに呼び出しますtypeid(NextArg).name()


私は車輪を再発明しましたか?

はい、そうではありません。Boost.Functionは、すべての引数 (argN_typeスタイル) の型定義と、その数の静的定数arityを提供します。ただし、これらの typedef に一般的に簡単にアクセスすることはできません。存在しないものに誤ってアクセスしないようにするための回り道が必要です。アイデアは最もtupleうまく機能しますが、より良い方法で書くことができます。これは、私がかつて書いたものの修正版です。

#include <tuple>
#include <type_traits>
#include <iostream>
#include <typeinfo>

namespace detail{

template<class T>
std::ostream& print_name(std::ostream& os);

template<class T>
std::ostream& print_pointer(std::ostream& os, std::true_type){
  typedef typename std::remove_pointer<T>:: type np_type;
  os << "pointer to ";
  return print_name<np_type>(os);
}

template<class T>
std::ostream& print_pointer(std::ostream& os, std::false_type){
  return os << typeid(T).name();
}

template<class T>
std::ostream& print_name(std::ostream& os, std::true_type){
  return os << "reference to " << typeid(T).name();
}

template<class T>
std::ostream& print_name(std::ostream& os, std::false_type){
  return print_pointer<T>(os, typename std::is_pointer<T>::type());
}

template<class T>
std::ostream& print_name(std::ostream& os){
  return print_name<T>(os, typename std::is_reference<T>::type());
}

// to workaround partial function specialization
template<unsigned> struct int2type{};

template<class Tuple, unsigned I>
std::ostream& print_types(std::ostream& os, int2type<I>){
  typedef typename std::tuple_element<I,Tuple>::type type;

  print_types<Tuple>(os, int2type<I-1>()); // left-folding
  os << ", ";
  return print_name<type>(os);
}

template<class Tuple>
std::ostream& print_types(std::ostream& os, int2type<0>){
  typedef typename std::tuple_element<0,Tuple>::type type;
  return print_name<type>(os);
}

} // detail::

template<class R, class... Args>
struct function_info{
  typedef R result_type;
  typedef std::tuple<Args...> argument_tuple;
  static unsigned const arity = sizeof...(Args);

  void describe_me(std::ostream& os = std::cout) const{
    using namespace detail;
    os << "I return '"; print_name<result_type>(os);
    os << "' and I take '" << arity << "' arguments. They are: \n\t'";
    print_types<argument_tuple>(os, int2type<arity-1>()) << "'\n";
  }
};

Ideone での実例。出力:

main:   I return 'i' and I take '2' arguments. They are: 
        'i, pointer to pointer to c'
x:      I return 'Ss' and I take '3' arguments. They are: 
        'i, Ss, c'
y:      I return 'Ss' and I take '3' arguments. They are: 
       'i, reference to Ss, c'
于 2012-01-13T00:24:55.343 に答える
1

ラムダ関数を使用した回答へのリンクは、重要なヒントを提供します。関数呼び出し演算子のメンバー関数へのポインターを取得する必要があります。つまり、Tが関数オブジェクトの場合、 を見る必要があります&T::operator()。一般化された SFINAE を使用すると、この関数呼び出し演算子が存在するかどうかを判断できます。このようなものを多少回りくどい方法でまとめると、次のようになります (clang ではまだサポートされていないラムダ関数を除いて、最近のバージョンの gcc と clang でコンパイルされます)。

#include <iostream>
#include <sstream>
#include <string>
#include <typeinfo>
#include <functional>
#include <utility>

// -----------------------------------------------------------------------------

struct S {
    void f(int, std::string&, void (*)(int)) {}
    void g(int, std::string&, void (*)(int)) const {}
};

// -----------------------------------------------------------------------------

template <typename T> struct describer;
template <> struct describer<S>;
template <> struct describer<int>;
template <> struct describer<void>;
template <> struct describer<std::string>;
template <typename T> struct describer<T&>;
template <typename T> struct describer<T*>;
template <typename T> struct describer<T const>;
template <typename T> struct describer<T volatile>;
template <typename T> struct describer<T const volatile>;
template <typename T, int Size> struct describer<T(&)[Size]>;

template <typename T> struct describer {
    static std::string type() { return "???"; }
};
template <> struct describer<S> {
    static std::string type() { return "S"; }
};
template <> struct describer<void> {
    static std::string type() { return "void"; }
};
template <> struct describer<int> {
    static std::string type() { return "int"; }
};
template <> struct describer<std::string> {
    static std::string type() { return "std::string"; }
};
template <typename T> struct describer<T&> {
    static std::string type() { return describer<T>::type() + std::string("&"); }
};
template <typename T> struct describer<T&&> {
    static std::string type() { return describer<T>::type() + std::string("&&"); }
};
template <typename T> struct describer<T*> {
    static std::string type() { return describer<T>::type() + std::string("&"); }
};
template <typename T> struct describer<T const> {
    static std::string type() { return describer<T>::type() + std::string(" const"); }
};
template <typename T> struct describer<T volatile> {
    static std::string type() { return describer<T>::type() + std::string(" volatile"); }
};
template <typename T> struct describer<T const volatile> {
    static std::string type() { return describer<T>::type() + std::string(" const volatile"); }
};
template <typename T, int Size> struct describer<T(&)[Size]>
{
    static std::string type() {
        std::ostringstream out;
        out << "(array of " << Size << " " << describer<T>::type() << " objects)&";
        return out.str();
    }
};

template <typename... T> struct description_list;
template <> struct description_list<> { static std::string type() { return std::string(); } }; 
template <typename T> struct description_list<T> { static std::string type() { return describer<T>::type(); } };
template <typename T, typename... S> struct description_list<T, S...> {
    static std::string type() { return describer<T>::type() + ", " + description_list<S...>::type(); }
};

template <typename R, typename... A>
struct describer<R(*)(A...)>
{
    static std::string type() {
        return "pointer function returning " + describer<R>::type() + " and taking arguments"
            + "(" + description_list<A...>::type() + ")";
    }
};

template <typename R, typename S, typename... A>
struct describer<R(S::*)(A...)>
{
    static std::string type() {
        return "pointer to member function of " + describer<S>::type() + " returning " + describer<R>::type() + " "
            "and taking arguments" + "(" + description_list<A...>::type() + ")";
    }
};

template <typename R, typename S, typename... A>
struct describer<R(S::*)(A...) const>
{
    static std::string type() {
        return "pointer to const member function of " + describer<S>::type() + " returning " + describer<R>::type() + " "
            "and taking arguments" + "(" + description_list<A...>::type() + ")";
    }
};

template <typename T> char (&call_op(decltype(&T::operator())*))[1];
template <typename T> char (&call_op(...))[2];

template <typename T> struct has_function_call_operator { enum { value = sizeof(call_op<T>(0)) == 1 }; };

template <typename T>
typename std::enable_if<!has_function_call_operator<T>::value>::type describe(std::string const& what, T)
{
    std::cout << "describe(" << what << ")=" << describer<T>::type() << "\n";
}

template <typename T>
typename std::enable_if<has_function_call_operator<T>::value>::type describe(std::string const& what, T)
{
    std::cout << "describe(" << what << ")=function object: " << describer<decltype(&T::operator())>::type() << "\n";
}


int f(std::string, std::string const&, std::string&&) { return 0; }
int g(std::string&, std::string const&) { return 0; }

int main()
{
    describe("int", 1);
    describe("f", &f);
    describe("g", &g);
    describe("S::f", &S::f);
    describe("S::g", &S::g);
    describe("mini-lambda", []{}); // doesn't work with clang, yet.
    describe("std::function<int(int(&)[1], int(&)[2], int(&)[4], int(&)[4])>",
             std::function<int(int(&)[1], int(&)[2], int(&)[4], int(&)[4])>());
}
于 2012-01-13T00:28:59.637 に答える