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?
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
I think it's just that someone got the idea that you could (ab)use the function type notation to mimic the way the respective functor call would look like, and it stuck. So, no technical reasons, just an aesthetic one.
// the result type of a call to (an object of) type F,
// passing (objects of) types A, B, and C as parameters.
result_of<F(A, B, C)>::type
result_of
was part of TR1, which came out before decltype was added to the language. But it was designed with decltype
in mind, so changing the implementation of result_of
to use decltype
is simple. Yes, it's a hack, but it works.
(This expands on JohannesD's answer and Jesse Good's comment on it, but this won't fit in a comment. Please upvote that other answer not this one.)
From N1454 Syntax and examples:
The definition of the behavior of result_of
is straightforward: given types F
, T1
, T2
, ..., TN
and lvalues f
, t1
, t2
, ..., tN
of those types, respectively, the type expression
result_of<F(T1, T2, ..., TN)>::type
evaluates to the type of the expression f(t1, t2, ..., tN)
.
This is not abusing the type system, it's beautifully elegant!