Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::result_of take an (unrelated) function type as a type argument?

Tags:

I realize "why are things the way they are" questions are not usually the best, but there are many people on SO that are tuned to standard committee discussions so I hope this can be answered factually, as I'm legitimately curious as to what the answer is.

Basically, it took me a long time to figure out what was going on with std::result_of's template signature the first time I saw it: I thought it was an entirely new construct for template parameters that I had never seen before.

template< class F, class... ArgTypes >
class result_of<F(ArgTypes...)>;

After some time thinking about it, I realized what this actually was: F(ArgTypes...) is a function type, but it's not the type of the function whose result type is being evaluated (that's just F): it's the type of a function taking ArgTypes... arguments and returning type F.

Isn't this...odd? Kind of hackish? Does anyone know if the committee ever discussed any alternatives, like, say, the following...

template< class F, class... ArgTypes >
class result_of<F, ArgTypes...>;

?

I guess it's possible that there's situations where the second construct can't be used as easily as the first one, but which ones?

I'm not trying to pass judgement on this, but it's just that this was legitimately confusing to me the first time I saw it, so I'm curious if there's a good reason for it. I realize part of the answer might simply be "because Boost did it" that way, but still that leave the remaining (factual) questions...

  • Is there a technical reason Boost choose this syntax to encode type information rather than any alternative?

  • Was there any discussion by the C++11 committee about how appropriate it was to standardize this, given that std::result_of can be implemented in terms of decltype fairly easily anyway?

like image 935
Stephen Lin Avatar asked Mar 18 '13 21:03

Stephen Lin


1 Answers

Having a function-type as the parameter allows you to have an unrestricted "variadic" class template even in C++03. Think about it: In C++03, we didn't have variadic templates. And you can't "overload" a class template like you can with function templates - so how would it be otherwise possible to allow different amounts of "arguments" to the function?

Using a function type, you can just add any number partial specializations for the different number of parameters:

template<class Fty>
struct result_of;

template<class F>
struct result_of<F()>{ /*...*/ };

template<class F, class A0>
struct result_of<F(A0)>{ /*...*/ };

template<class F, class A0, class A1>
struct result_of<F(A0, A1)>{ /*...*/ };

// ...

The only other way to do this in C++03 is default template arguments and partially specializing for every case - the disadvantage being that it doesn't look like a function call anymore, and that any kind of wrapper that uses result_of internally can't just pass Sig along.


Now, there's one disadvantage with the function-type way - you also get all the usual transformations done to the "parameters": R(Args...) -> R(*)(Args...) and more importantly T[N] -> T* and top-level cv-qualifiers being discarded (§8.3.5/5):

struct X{
  bool operator()(int (&&arr)[3]);
  long operator()(void*);
};

static_assert(std::is_same<std::result_of<X(int[3])>::type, bool>(), "/cry");

Live example. Output:

error: static assertion failed: /cry

The other problems is with the top-level cv-qualifiers being discarded:

struct Y{};

struct X{
  bool operator()(Y const&);
  long operator()(Y&&);
};

Y const f();

static_assert(std::is_same<std::result_of<X(Y const)>::type, bool>(), "/cry");

Live example. Output:

error: static assertion failed: /cry

like image 93
Xeo Avatar answered Sep 28 '22 11:09

Xeo