A proposal to add lambda/function traits to the STL

802 views
Skip to first unread message

Emil Madsen

unread,
Nov 6, 2013, 1:35:18 PM11/6/13
to std-pr...@isocpp.org
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.

Have a look,
I'm open to all kinds of criticism and feedback :)

// Emil 'Skeen' Madsen.
Proposal_to_add_lambda_traits.pdf

Nevin Liber

unread,
Nov 6, 2013, 1:44:26 PM11/6/13
to std-pr...@isocpp.org
On 6 November 2013 12:35, Emil Madsen <sov...@gmail.com> wrote:

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.

How does this work with C++14's polymorphic lambdas?
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Emil Madsen

unread,
Nov 6, 2013, 1:59:00 PM11/6/13
to std-pr...@isocpp.org

My best guess would be, that it has to behave as with overloaded functions, that is;
<code>
long overloaded_function(int i);
long overloaded_function(double d);

using traits = function_traits<decltype(overloaded_function)>;
</code>
Which yields;
<code>
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.

Emil Madsen

unread,
Nov 6, 2013, 2:03:22 PM11/6/13
to std-pr...@isocpp.org

On Wednesday, 6 November 2013 19:44:26 UTC+1, Nevin ":-)" Liber wrote:
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.

Nevin Liber

unread,
Nov 6, 2013, 2:32:56 PM11/6/13
to std-pr...@isocpp.org
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.

mcy...@gmail.com

unread,
Nov 6, 2013, 3:37:18 PM11/6/13
to std-pr...@isocpp.org
I can present a solution that I came up with which is no more than a sketch, but the general approach is to use SFINAE and specify the known template arguments in order to force a specialization in the case where the function is a template.

/* forward declaration */
template <typename Signature>
struct function_traits;

/* Member function. */
template <typename R, typename Class, typename... Args>
struct function_traits<R (Class::*)(Args...)> : public std::integral_constant<size_t, sizeof...(Args)> {

  using return_type = R;

  using type = R (Args...);
};

/* Const member function. */
template <typename R, typename Class, typename... Args>
struct function_traits<R (Class::*)(Args...) const> : public std::integral_constant<size_t, sizeof...(Args)> {

  using return_type = R;

  using type = R (Args...);
};

/* Takes a callable and a list of 0 or more template parameters to specialize the template with. */
template <typename Callable, typename... TemplateArgs>
struct callable_traits;

/* Non-templated operator(). */
template <typename Callable>
struct callable_traits<Callable> : public function_traits<decltype(&Callable::operator())> {};

/* Templated operator(). */
template <typename Callable, typename... TemplateArgs>
struct callable_traits : public function_traits<decltype(&Callable::template operator()<TemplateArgs...>)> {};

/* Nevin's PolyStruct class. */
struct PolyStruct {
  
  template <typename T>
  T operator()(T t) { return t; }

};  // PolyStruct

/* Convenience type alias for callable_traits<>. */
template <typename Class, typename... TemplateArgs>
using callable_traits_t = typename callable_traits<Class, TemplateArgs...>::type;

/* Test. */
static_assert(std::is_same<callable_traits_t<PolyStruct, int>, int (int)>::value, "");

int main() {}

Tested with g++4.7.3 and clang++3.3

Emil Madsen

unread,
Nov 6, 2013, 3:57:59 PM11/6/13
to std-pr...@isocpp.org
On Wednesday, 6 November 2013 20:32:56 UTC+1, Nevin ":-)" Liber wrote:
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.
In order to retrieve the function_traits for the int version, of the PolyStruct;
<code>
using traits = function_traits<decltype(&PolyStruct::operator()<int>)>;
 </code>
This will however not be functional with the currently suggested solution, however it seems 'mcypark' has found an application-able solution.

 
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.
I fully agree with this!
 

mcy...@gmail.com

unread,
Nov 6, 2013, 4:16:50 PM11/6/13
to std-pr...@isocpp.org
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.

Emil 'Skeen' Madsen

unread,
Nov 6, 2013, 4:29:48 PM11/6/13
to std-pr...@isocpp.org
On Wednesday, 6 November 2013 22:16:50 UTC+1, mcy...@gmail.com wrote:
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>)>
I agree with this, and I honestly really like your solution better, than the one I suggested myself.
 
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
// Test functions for specific arity
template<typename Callable>
struct is_function_unary : std::integral_constant<bool, function_arity<Callable>::value == 1> {};
template<typename Callable>
struct is_function_binary : std::integral_constant<bool, function_arity<Callable>::value == 2> {};
template<typename Callable>
struct is_function_ternary : std::integral_constant<bool, function_arity<Callable>::value == 3> {};
// Check for 'n' arity
template<typename Callable, size_t arity>
struct is_function_nary : std::integral_constant<bool, function_arity<Callable>::value == arity> {};
// Get types of arguments
function_return<Callable>::type // Yield the Callable's return type
function_arg<N, Callable>::type // Yield the Callable's n'th argument type
</code>
Which may then likely be implemented using the suggested 'callable_traits' approach, but I honestly believe this would be more in the style of the current traits.

Nevin Liber

unread,
Nov 6, 2013, 4:53:40 PM11/6/13
to std-pr...@isocpp.org
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>::value

In general, this is hard:

int foo(int, int = 0);

Is the arity 1 or 2?

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.

Emil 'Skeen' Madsen

unread,
Nov 6, 2013, 5:07:18 PM11/6/13
to std-pr...@isocpp.org
On Wednesday, 6 November 2013 22:53:40 UTC+1, Nevin ":-)" Liber wrote:
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>::value

In general, this is hard:

int foo(int, int = 0);

Is the arity 1 or 2?

'foo' has arity 2,
atleast when dealing with std::function;
<code>
std::function<int(int)> function = foo;
</code>
Yields;
<code>
main.cpp:10:29: error: no viable conversion from 'int (int, int)' to 'std::function<int (int)>'
    std::function<int(int)> function = func;
                            ^          ~~~~
</code>
While 'std::function<int(int, int)>' seems to compile.



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.
Obviously this trait, should be defined as 'mcypark' did in his snippet, that is <Callable, Args...> such that one can specify the exact function they want to get the arity of.

Nevin Liber

unread,
Nov 6, 2013, 5:29:07 PM11/6/13
to std-pr...@isocpp.org
If I knew the exact function, I don't need the trait to calculate the arity!

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
{
    // compare
    bool operator()(A const&, A const&) const;
};

// and somewhere else in the code
auto arity = function_arity<Unordered>::value;

// Now we go in and add to Unordered:

struct Unordered
{
    // compare
    bool operator()(A const&, A const&) const;

    // compare with other types
    template<typename B>
    bool operator()(A const&, B const&) const;

    template<typename B>
    bool operator()(B const&, A const&) const;

    // hash
    size_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.

Emil 'Skeen' Madsen

unread,
Nov 6, 2013, 6:19:05 PM11/6/13
to std-pr...@isocpp.org
I'm considering the case, where you pass a specific function/lambda to a templated function, which may accept it unrestricted and then need to dig out that information again.
 
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
{
    // compare
    bool operator()(A const&, A const&) const;
};

// and somewhere else in the code
auto arity = function_arity<Unordered>::value;

// Now we go in and add to Unordered:

struct Unordered
{
    // compare
    bool operator()(A const&, A const&) const;

    // compare with other types
    template<typename B>
    bool operator()(A const&, B const&) const;

    template<typename B>
    bool operator()(B const&, A const&) const;

    // hash
    size_t operator()(A const&) const;
};

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.
I know that there's a huge difference between a subtle change in semantics and a compiler error.
Also what you code does is reasonable, and would be something to support.

This "ambiguity" made things like boost::bind hard to use; I don't want to exasperate the problem.
Also, I believe the way that std::bind avoids this ambiguity, is to allow you to specify the exact function you want.
<code>
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.
This is the exact solution that I'm suggesting for the exact same problem, in lambda_traits.
Also when using bind, without the explicit template specification adding an overload may indeed incur a compiler error at callee.

Nevin Liber

unread,
Nov 6, 2013, 6:35:34 PM11/6/13
to std-pr...@isocpp.org
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.

Jeffrey Yasskin

unread,
Nov 6, 2013, 8:38:16 PM11/6/13
to std-pr...@isocpp.org
Have you seen http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3729.html?
> --
>
> ---
> You received this message because you are subscribed to the Google Groups
> "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.
> Visit this group at
> http://groups.google.com/a/isocpp.org/group/std-proposals/.

Emil 'Skeen' Madsen

unread,
Nov 11, 2013, 8:32:23 AM11/11/13
to std-pr...@isocpp.org
On Thursday, 7 November 2013 00:35:34 UTC+1, Nevin ":-)" Liber wrote:
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).

I wouldn't say that it's bad, just that you'll have to specify which overload you like, because honestly if you don't, how am I as a programmer supposed to know which overload you meant to get the arity, ect. off? - And how is the compiler supposed to know?

As for the usefulness in generic constructs, I do believe it's useful, you'll just have to pass the explicit template parameters to the generic function, which makes use of the lambda traits, that is, you bump up the responsibility to make it work, also this is only needed when you use overloaded functions. Also without it, how am I as the maintainer supposed to know which overload you're referring to?

Also I'd rather have lambda/function traits with the need for specifying which function you meant, whenever you're dealing with overloading, than not having them at all.
 
 

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.
 I can show you that code, and it does not require a horrible cast, all it requires is an explicit template specification;
<code>
int main()
{
    auto callable = std::bind<int(int,int)>(my_divide,10,2);
}
</code>
And I honestly don't mind requiring the programmer to tell the maintainer (and the compiler), which version we're referring to. Also I do truly agree that syntax matters a lot, but I'd rather have lousy syntax and be able to solve my issue, than not being able to solve it at all.

Emil 'Skeen' Madsen

unread,
Nov 11, 2013, 8:39:53 AM11/11/13
to std-pr...@isocpp.org
I haven't seen this proposal before, but even though there's a common goal, there's a difference in implementation; compiler-supported / library-only.

morw...@gmail.com

unread,
Nov 13, 2013, 4:09:35 PM11/13/13
to std-pr...@isocpp.org
Something that came up today on StackOverflow: http://stackoverflow.com/questions/19961873/test-if-a-lambda-is-stateless

You might want to add such a trait to your proposal to check whether a lambda is stateless :)

Billy O'Neal

unread,
Nov 13, 2013, 4:11:15 PM11/13/13
to std-proposals
What does it mean that a lambda is stateless?

Billy O'Neal
Malware Response Instructor - BleepingComputer.com


--

Ville Voutilainen

unread,
Nov 13, 2013, 4:22:36 PM11/13/13
to std-pr...@isocpp.org
On 13 November 2013 23:09, <morw...@gmail.com> wrote:
> Something that came up today on StackOverflow:
> http://stackoverflow.com/questions/19961873/test-if-a-lambda-is-stateless
>
> You might want to add such a trait to your proposal to check whether a
> lambda is stateless :)

Perhaps that's why he already commented the SO discussion:
"I'm going to add something alike this to my standards proposal. – Skeen"
;)

morw...@gmail.com

unread,
Nov 13, 2013, 4:23:17 PM11/13/13
to std-pr...@isocpp.org
It means that its capture list is empty. This can be useful since a lambda without a capture list can be converted to a function pointer.

Billy O'Neal

unread,
Nov 13, 2013, 4:27:15 PM11/13/13
to std-proposals
Not quite -- this detection fails for other stateless functions, e.g.
 
struct MyFunctor
{
    // Stateless
    void operator()(int x)
    {
        // ...
    }
};

If you want to test whether the thing is convertible to a function pointer, use std::is_convertible -- it already exists and answers the question you really are asking here.

Billy O'Neal
Malware Response Instructor - BleepingComputer.com


morw...@gmail.com

unread,
Nov 13, 2013, 6:05:13 PM11/13/13
to std-pr...@isocpp.org
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. I don't know whether this is a good thing though.

Nevin Liber

unread,
Nov 13, 2013, 6:21:28 PM11/13/13
to std-pr...@isocpp.org
On 13 November 2013 17:05, <morw...@gmail.com> wrote:
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.

How about just using std::is_empty?

morw...@gmail.com

unread,
Nov 13, 2013, 6:32:55 PM11/13/13
to std-pr...@isocpp.org
Yet another note:

* arity could be made static constexpr instead of static const.
* args could be an alias template instead of a struct. Therefore, only arg<...>::type can be simplified to arg<...> everywhere. We have alias templates now, let's use them! :)


Le mercredi 6 novembre 2013 19:35:18 UTC+1, Emil 'Skeen' Madsen a écrit :

morw...@gmail.com

unread,
Nov 27, 2013, 2:14:45 PM11/27/13
to std-pr...@isocpp.org
Another function-related trait that could be useful would be a trait to check whether a given function is a predicate or not.


Le mercredi 6 novembre 2013 19:35:18 UTC+1, Emil 'Skeen' Madsen a écrit :

Billy O'Neal

unread,
Nov 27, 2013, 2:40:43 PM11/27/13
to std-proposals
What do you mean by a predicate? Just that it returns bool? If so, you can already find that out using result_of.

Billy O'Neal
Malware Response Instructor - BleepingComputer.com


--

morw...@gmail.com

unread,
Nov 27, 2013, 4:18:33 PM11/27/13
to std-pr...@isocpp.org
I mean by predicate what I often saw as the definition of a predicate in C++: a function which takes one value (by value or const reference) and which returns an instance of a class that is convertible to bool. It's a little bit more boilerplate than just returning a bool.

Andrew Tomazos

unread,
Nov 28, 2013, 12:04:27 AM11/28/13
to std-pr...@isocpp.org
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.

Perhaps std::is_member_function<F> (only true for non-static member functions), and std::implicit_object_type<F> gives a class ::type member with appropriate cv/ref qualification that match ref-qualfier and cv-qualifier-seq.

Anyway, just some ideas.

Andrew Tomazos

unread,
Nov 28, 2013, 12:07:52 AM11/28/13
to std-pr...@isocpp.org


On Wednesday, November 6, 2013 10:53:40 PM UTC+1, Nevin ":-)" Liber wrote:
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>::value

In general, this is hard:

int foo(int, int = 0);

Is the arity 1 or 2?


2.  Default arguments are not part of a function type.
 

Emil 'Skeen' Madsen

unread,
Nov 28, 2013, 12:54:41 PM11/28/13
to std-pr...@isocpp.org

On Thursday, 28 November 2013 06:04:27 UTC+1, Andrew Tomazos wrote:
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.

I was aiming to do it without compiler support, in contrast to n3729 by Mike Spertus. My proposal is a library extension only.
 

Secondly, I think if it turns out being worth adding, the interface would be more consistent with existing traits being separate:
 
I agree with this, and when I get time to finish up the second revision, of the proposal I'll be proposing an interface which is alike what you suggest.
I expect to have the next revision up for discussion some time next week.
 
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?

The code I've currently got running, will return 3 as arity for 'R(A, B, C, ...)', while also returning true for what you suggest as 'std::has_varying_arguments<F>'. Parameter types will only be defined for [0, ..., arity].
 

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?

I do believe that std::parameter_type should retain qualifiers. If we don't want them, then we're able to get rid of them using "std::remove_const" and friends.
As for the implicit object parameter; My initial opinion would be, that this should actually also be a part of the arity, and parameter_type, as we can again simply get rid of it, if we want to, but I'm open to discussion!
 
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.

I can agree with all of these!
 
Reply all
Reply to author
Forward
0 new messages