I just finished writing the first draft of my first proposal ever.
And before submitting it, I'd like to discuss the matter at hand, and the proposal at this forum.
The proposal is about adding traits, to determine a lambdas return and argument type(s) at compile time, to the standard library.
main.cpp: In function ‘int main()’:
main.cpp:95:56: error: decltype cannot resolve address of overloaded function
using traits2 = function_traits<decltype(functionty)>
^
main.cpp:95:57: error: template argument 1 is invalid
using traits2 = function_traits<decltype(functionty)>
</code>
On g++ 4.8, and;
<code>
main.cpp:95:46: error: reference to overloaded function could not be resolved; did you mean to call it?
using traits2 = function_traits<decltype(functionty)>
^~~~~~~~~~
</code>
On clang 3.4.
On Wednesday, 6 November 2013 19:44:26 UTC+1, Nevin ":-)" Liber wrote:How does this work with C++14's polymorphic lambdas?
That is, in this case you'll have to specifying exactly which version you want, by explicitly passing the required templates.
I believe this is a part of the reason why std::function requires you, to specify return type and parameter type(s) in the template parameter, as overloaded functions are otherwise ambiguous.
On 6 November 2013 13:03, Emil Madsen <sov...@gmail.com> wrote:On Wednesday, 6 November 2013 19:44:26 UTC+1, Nevin ":-)" Liber wrote:How does this work with C++14's polymorphic lambdas?That is, in this case you'll have to specifying exactly which version you want, by explicitly passing the required templates.
Unless I'm reading it wrong, function_traits only takes one template parameter (the lambda), so how exactly do you do that? Could you show us working code with the following type:
struct PolyStruct
{
template<typename T>
T operator()(T t) { return t; }
};and I want to call it with an int.
I believe this is a part of the reason why std::function requires you, to specify return type and parameter type(s) in the template parameter, as overloaded functions are otherwise ambiguous.In C++14, I fully expect that 90+% of the non-legacy lambdas that are called with at least one parameter will be declared polymorphically. It needs to be useable with this (predicted) common case.
Emil: That would work, however the reason why I didn't suggest it is because I think Nevin was hinting that PolyStruct is actually the underlying implementation of the polymorphic lambda.using traits = function_traits<decltype(&PolyStruct::operator()<int>)>;Makes it so that we have to get at the operator() ourselves.If we look at the symmetric case for the non-templated version,Given, auto lambda = [](int) { return 42; };We were able to say: function_traits<decltype(lambda)>As opposed to, function_traits<decltype(&decltype(lambda)::operator())>
I think given a polymorphic lambda like so: auto lambda = [](x) { return x; }We should be able to say: function_traits<decltype(lambda), int>Rather than: function_traits<decltype(&decltype(lambda)::operator()<int>)>
Now, a few other things I considered in introducing the callable_traits<> is because:I wanted function_traits<> to take 1 template parameter, Signature, given function-pointer, pointer-to-member-function, etc.and callable_traits<> to take a callable and N (N >= 0) template parameters to pass an operator() to function_traits.It's a simple sketch and a few considerations that I hope will help your proposal.
However I came to think about it, maybe what we actually want, is something more in the style of traits, I'm thinking in the lines of;
<code>
// Yields the Callable's arity
function_arity<Callable>::value
On 6 November 2013 15:29, Emil 'Skeen' Madsen <sov...@gmail.com> wrote:However I came to think about it, maybe what we actually want, is something more in the style of traits, I'm thinking in the lines of;
<code>
// Yields the Callable's arity
function_arity<Callable>::valueIn general, this is hard:int foo(int, int = 0);Is the arity 1 or 2?
main.cpp:10:29: error: no viable conversion from 'int (int, int)' to 'std::function<int (int)>'
std::function<int(int)> function = func;
^ ~~~~</code>
Moving to a function object case:
struct PolyStruct2
{template<typename... Ts>void operator()(Ts&&... ts) { /* ... */ }
};Use of such a type trait would be very fragile. If you have something with exactly one signature, all it takes is adding another signature and the code breaks.
For many function objects and function, the concept of arity, parameter type and return type doesn't have a single answer, as those things are based on how it is being used, not how it has been defined.
I don't want to run into the case where:struct Unordered
{// comparebool operator()(A const&, A const&) const;};
// and somewhere else in the codeauto arity = function_arity<Unordered>::value;// Now we go in and add to Unordered:struct Unordered
{// comparebool operator()(A const&, A const&) const;
// compare with other typestemplate<typename B>bool operator()(A const&, B const&) const;
template<typename B>bool operator()(B const&, A const&) const;// hashsize_t operator()(A const&) const;};
and now everything which already used the type traits is broken.
This "ambiguity" made things like boost::bind hard to use; I don't want to exasperate the problem.
and now everything which already used the type traits is broken.I'm going to make a semi bold statement, which is this;
Given the following struct;
<code>
struct magic_functions
{
static void func(double d)
{
std::cout << "double";
}
}
</code>
And call code which does simply invokes "func(2)". Then adding a function;
<code>
static void func(int d)
{
std::cout << "int";
}
</code>
To the struct, may break the callee's code, likely not in this case, but assuming 'func' did something.
My point being, that whenever you overload something, then you possibly change the semantics and behavior for the callee.
int my_divide(int, int);
int my_divide(double, double);
int main()
{
// error: no matching function for call to ‘bind(<unresolved overloaded function type>, int, int)’
auto callable = std::bind(my_divide,10,2);
}
</code>
Where the solution, is to call bind with template arguments to specify which version you want.
On 6 November 2013 17:19, Emil 'Skeen' Madsen <sov...@gmail.com> wrote:and now everything which already used the type traits is broken.I'm going to make a semi bold statement, which is this;
Given the following struct;
<code>
struct magic_functions
{
static void func(double d)
{
std::cout << "double";
}
}
</code>
And call code which does simply invokes "func(2)". Then adding a function;
<code>
static void func(int d)
{
std::cout << "int";
}
</code>
To the struct, may break the callee's code, likely not in this case, but assuming 'func' did something.
Implicit conversions are a problem, and we tend to try and find ways to not exasperate it. IIRC, that was one of the motivations behind nullptr_t.My point being, that whenever you overload something, then you possibly change the semantics and behavior for the callee.I really don't want a type trait where passing a type with overloaded functions is bad. It isn't terribly useful in generic constructs (one of your motivating cases).
int my_divide(int, int);
int my_divide(double, double);
int main()
{
// error: no matching function for call to ‘bind(<unresolved overloaded function type>, int, int)’
auto callable = std::bind(my_divide,10,2);
}
</code>
Where the solution, is to call bind with template arguments to specify which version you want.
Why don't you show that code? Hint: it's a pretty horrible cast that I wouldn't want to inflict on anyone. It's one of the prime motivating cases for using lambdas over std::bind. Syntax matters.
--
Woah, beats me, I didn't see that one! :D
@Billy: Well, at least with is_stateless, you don't really have to care about the exact function pointer type right from the start.
--
On 6 November 2013 15:29, Emil 'Skeen' Madsen <sov...@gmail.com> wrote:
However I came to think about it, maybe what we actually want, is something more in the style of traits, I'm thinking in the lines of;
<code>
// Yields the Callable's arity
function_arity<Callable>::valueIn general, this is hard:int foo(int, int = 0);Is the arity 1 or 2?
Hi Emil,Here are a few ideas:First, I think your proposed traits can be created without compiler support as handful of pure library code using type deduction.
Secondly, I think if it turns out being worth adding, the interface would be more consistent with existing traits being separate:
For F where std::is_function<F> is true:- std::return_type<F>- std::arity<F>- std::parameter_type<F,I>namespace std{template<typename F>struct return_type{static_assert(std::is_function<F>);using type = __return_type(F);};template<typename F>struct parameter_list_size : integer_constant<size_t, __arity(F)>{static_assert(std::is_function<F>);};template<typename P, size_t I>struct parameter_type{static_assert(std::is_function<F>);static_assert(I >= 0 && I < std::arity<F>);using type = __parameter_type(P, I);}};Combined with the std::invocation_type transformation to produce a function type from any callable such as a function object or lambda, this should give you most of the information you might want.Thirdly, what about function types with varying number of arguments? ie a function type with an ellipse like R(A, B, C, ...), like printf. What shall std::arity<F> and std::parameter_type<F,I> return for those function types?
Forthly, what about the implicit object parameter of non-static member functions (the ref-qualifier, and the cv-qualifier-seq)? Note that:> The return type, the parameter-type-list, the ref-qualifier, and the cv-qualifier-seq, but not the default arguments (8.3.6) or the exception specification (15.4), are part of the function type.What shall std::arity and std::parameter_type return for them?
Perhaps there need to be additional traits to inspect:- the presence of an ellipse in the parameter-list (std::has_varying_arguments<F>)- whether it is a non-static member function type- and if so the ref-qualifier and cv of the implicit object parameter.