The output of std :: function with more than two arguments

I wonder why std :: function knows only about two-parameter functions. I wrote code that works well, but there are a number of limitations. Any feedback is appreciated. In particular, I suspect that I am inventing a wheel.

My code is on ideone and I will reference it.

For example, I can describe the type main with:

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

(where 'i' means 'int' and 'PPc' means a pointer to a pointer to a char)

The standard std :: function does not work with functions with more than two arguments (see the last two lines of my code), but this code (the code example demonstrates functions with three arguments). Perhaps my design should be used in the standard library! I define typedef tuple<Args...> args_as_tuple; to store all arguments, not just the first two types of arguments.

The main trick is the deduction in this function:

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

Limitations:

  • This does not work with lambdas. This will not compile function_type_deducer([](){}).describe_me();
  • He doesn’t notice that there is a slight difference between x and y , since y takes string& , where x takes string . (std :: function doesn't notice it either)

Any ideas on how to fix this? Did I invent a wheel?

+3
c ++ c ++ 11 templates
source share
2 answers

This will not compile function_type_deducer([](){}).describe_me();

It would work if function_type_deducer not a template. :) Lacking lambdas (empty [] ) are implicitly converted to function pointers. Unfortunately, implicit conversions are not taken into account when outputting some template arguments. See this question for more information (note that my answer is not entirely correct, as the comments indicate).


He does not notice that there is a slight difference between x and y, since y takes the string &, where x takes the string.

This is not a function problem, a typeid , as this simple test code shows:

 template<class T> void x(void(T)){ T v; (void)v; } void f1(int){} void f2(int&){} int main(){ x(f1); x(f2); } 

Live example on Ideone . Exit:

error: 'v declared as reference but not initialized

A simple fix would be to use tag dispatch:

 #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()); } 

And call print_name<NextArg>() instead of typeid(NextArg).name() .


Did I reinvent the wheel?

Yes, it seems and no, you didn’t. Boost.Function provides a typedef for all arguments ( argN_type style), as well as a static constant arity for its number. However, you cannot easily access these typedefs in general. You will need a workaround in order not to accidentally gain access to nonexistent ones. The idea of tuple works best, but you can write it beautifully. Here's a modified version of what I once wrote:

 #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"; } }; 

Live example on Ideone . Exit:

 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' 
+5
source share

The link to the answer using the lambda function provides a critical hint: you need to get a pointer to a member function for the function call statement. That is, if T is a functional object, you need to look at &T::operator() . Using the generic SFINAE, you can determine if this function call statement exists. Combining this stuff, perhaps in a somewhat workaround, it gives (which compiles with the latest version of gcc and clang, with the exception of the lambda function, which is not yet supported by 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])>()); } 
+1
source share

All Articles