Isn't the Template Argument (The Signature) of Std::Function Part of Its Type

Isn't the template argument (the signature) of std::function part of its type?

The problem is that both function<int()> and function<int(int)> are constructible from the same function. This is what the constructor declaration of std::function looks like in VS2010:

template<class _Fx>
function(_Fx _Func, typename _Not_integral<!_Is_integral<_Fx>::value, int>::_Type = 0);

Ignoring the SFINAE part, it is constructible from pretty much anything.

std::/boost::function employ a technique called type erasure, to allow arbitary objects/functions to be passed in, so long they satisfy the signature when being called. One drawback from that is, that you get an error in the deepest part of the implementation (where the saved function is being called) when supplying an object which can't be called like the signature wants it to, instead of in the constructor.


The problem can be illustrated with this little class:

template<class Signature>
class myfunc{
public:
template<class Func>
myfunc(Func a_func){
// ...
}
};

Now, when the compiler searches for valid functions for the overload set, it tries to convert the arguments if no perfect fitting function exists. The conversion can happen through the constructor of the parameter of the function, or through a conversion operator of the argument given to the function. In our case, it's the former.

The compiler tries the first overload of a. To make it viable, it needs to make a conversion. To convert a int(*)() to a myfunc<int()>, it tries the constructor of myfunc. Being a template that takes anything, the conversion naturally succeeds.

Now it tries the same with the second overload. The constructor still being the same and still taking anything given to it, the conversion works too.

Being left with 2 functions in the overload set, the compiler is a sad panda and doesn't know what to do, so it simply says the call is ambigious.


So in the end, the Signature part of the template does belong to the type when making declarations/definitions, but doesn't when you want to construct an object.


Edit:

With all my attention on answering the title-question, I totally forgot about your second question. :(

Can I circumvent it or will I have to keep the (annoying) explicit casts?

Afaik, you have 3 options.

  • Keep the cast
  • Make a function object of the appropriate type and pass that

    function<int()> fx = x;
    function<int(int)> fy = y;
    a(fx);
    a(fy);

  • Hide the tedious casting in a function and use TMP to get the right signature

The TMP (template metaprogramming) version is quite verbose and with boilerplate code, but it hides the casting from the client. An example version can be found here, which relies on the get_signature metafunction that is partially specialized on function pointer types (and provides a nice example how pattern matching can work in C++):

template<class F>
struct get_signature;

template<class R>
struct get_signature<R(*)()>{
typedef R type();
};

template<class R, class A1>
struct get_signature<R(*)(A1)>{
typedef R type(A1);
};

Of course, this needs to be extended for the number of arguments you want to support, but that is done once and then buried in a "get_signature.h" header. :)

Another option I consider but immediatly discarded was SFINAE, which would introduce even more boilerplate code than the TMP version.

So, yeah, that are the options that I know of. Hope one of them works for you. :)

type deduction for std::function argument types with auto adds const

The problem is that generic lambdas (auto param) are equivalent to a callable object whose operator() is templated. This means that the actual type of the lambda argument is not contained in the lambda, and only deduced when the lambda is invoked.

However in your case, by having specific std::function arguments, you force a conversion to a concrete type before the lambda is invoked, so there is no way to deduce the auto type from anything. There is no SFINAE in a non-template context.

With no specific argument type, both your call are valid overloads. Actually any std::function that can match an [](auto&) is valid. Now the only rule is probably that the most cv-qualified overload wins. You can try with a volatile float& and you will see it will still choose that. Once it choose this overload, the compilation will fail when trying to invoke.

Function signatures in C++ templates

The template parameter to std::function<Signature> is simply the type of a function, i.e., its signature. It uses the same notation as any function declaration except that it isn't named and the name is left out. You may have come across function pointers which use the same notation but the function signature is used for a pointer.

The reason std::function<Signature> (and apparently delegate<Signature>) are implemented using template specialization is to yield a nicer type:

template <typename T> class function;
template <typename R, typename... Args>
class function {
public:
R operator()(Args...);
// ...
};

template <typename R, typename... Args>
class other {
public:
R operator()(Args...);
// ...
};

int main() {
function<int(double, char)> f;
other<int, double, char> o;
}

Since the primary template for function<T> takes one type as argument, using the specialization the argument can be a normal function type. On the other hand, the same isn't done for other<T...> which, thus, gets a list of types.

It is worth nothing that std::function<T> objects can be passed around quite easily without any need to deal with many template arguments: since the function's signature is just a type, this class template takes just one template argument.

C++17: Deducing function noexcept specifier as non-type parameter

A non-type template parameter cannot be deduced from a noexcept-specifier.

[temp.deduct.type]/8 gives the list of contexts from which template parameters can be deduced. Essentially, it can be read as a list of ways to "unwrap" the argument type, and a list of positions in the unwrapped type from which template arguments can be deduced.

For example, the item T (T::*)(T) implies that if the parameter type and the argument type are both pointers to member functions, then template parameters can be deduced from the return type, the class type, and any argument types (for the member function), should they appear there.

You'll notice that there is no item of the form T() noexcept(i), T(T) noexcept(i), and so on.

Yet, some compilers choose to allow this kind of deduction anyway, probably because it is convenient. I would support adding it to the standard.

Failure to deduce template argument std::function from lambda function

A std::function is not a lambda, and a lambda is not a std::function.

A lambda is an anonymous type with an operator() and some other minor utility. Your:

auto foo = [](int i) {
std::cout << i << std::endl;
};

is shorthand for

struct __anonymous__type__you__cannot__name__ {
void operator()(int i) {
std::cout << i << std::endl;
}
};
__anonymous__type__you__cannot__name__ foo;

very roughly (there are actual convert-to-function pointer and some other noise I won't cover).

But, note that it does not inherit from std::function<void(int)>.


A lambda won't deduce the template parameters of a std::function because they are unrelated types. Template type deduction is exact pattern matching against types of arguments passed and their base classes. It does not attempt to use conversion of any kind.


A std::function<R(Args...)> is a type that can store anything copyable that can be invoked with values compatible with Args... and returns something compatible with R.

So std::function<void(char)> can store anything that can be invoked with a char. As int functions can be invoked with a char, that works.

Try it:

void some_func( int x ) {
std::cout << x << "\n";
}
int main() {
some_func('a');
some_func(3.14);
}

std::function does that some conversion from its signature to the callable stored within it.


The simplest solution is:

template <class F, class T>
void call(F f, T v) {
f(v);
}

now, in extremely rare cases, you actually need the signature. You can do this in c++17:

template<class T>
void call(std::function<void(T)> f, T v) {
f(v);
}
template<class F, class T>
void call(F f_in, T v) {
std::function f = std::forward<F>(f_in);
call(std::move(f), std::forward<T>(v));
}

Finally, your call is a crippled version of std::invoke from c++17. Consider using it; if not, use backported versions.

Can I use named parameters in std::function template signature type argument?

Yes, you can provide the parameter name. From paragraph §8.3.5/11 of the C++11 Standard:

An identifier can optionally be provided as a parameter name;

The same goes for pointer-to-function and pointer-to-member function type. Personally, I would use the identifier only if it expresses more clearly the intention of your code.

Compiler cannot deduce type of template function?

Template argument deduction works on the basis of the template parameters of the function, the signature of the function, and the types of the arguments provided. Nothing more. Even ignoring the fact that print is a template function (and therefore doesn't have a type that could be used for deduction), there is nothing in the signature of traverse that would give the compiler any idea that the type to deduce would be the type of print<std::string>. Any such information would be in the function's definition.

A definition that doesn't exist yet and cannot exist until traverse has been instantiated. And you can't instantiate a template until you have the template parameters. Which is what template argument deduction is intended to figure out.

Also, as previously mentioned, print is not a function; it is the name of a template. It has no type; it isn't even a function. It is a means for generating a function, based on template parameters. Even if you tried to pass print to a function that wasn't a template, the code still wouldn't work. The compiler can perform template argument deduction for functions only when you call them, not when you pass them as function arguments. In all non-deduced cases, you have to provide the template arguments directly.

Does std::function's copy-constructor require the template type's argument types to be complete types?

Edit: Apperently, this issue is now fixed, so the below text can be seen as history. :)


The issue is indeed (as I predicted) with libc++'s SFINAE checks in the templated ctor (for a reasoning, check this question). It checks if the following (for example) is valid and gives a nice and clean error at the construction site rather than deep inside the guts of std::function (try the following example with libstd++ or MSVC... shudder):

#include <functional>

void f(int* p){}

int main(){
std::function<void(int)> fun(f);
}

libc++ will cause the compiler to spit out something along the lines of "no constructor found that matches the argument list void (*)(int*)", since the only applicable one (the templated ctor) gets SFINAE'd out.

However, so that the __callable and __invoke_imp checks work, the argument and return types need to be complete, since otherwise implicit conversions wouldn't be taken into account here.

The reason that the templated ctor is even looked at is that all ctors are enumerated before considering a best match (in this case the copy ctor).


Now, the standard is very clear that the argument and return types need to be complete when constructing a std::function object from a callable object (aka calling the templated ctor):

§20.8.11.2.1 [func.wrap.func.con] p7

template <class F> function(F f);
template <class F, class A> function(allocator_arg_t, const A& a, F f);

Requires: F shall be CopyConstructible. f shall be Callable (20.8.11.2) for argument types ArgTypes and return type R. [...]

(Note: "Requires" addresses the user of the functionality, not the implementer.)

§20.8.11.2 [func.wrap.func] p2

A callable object f of type F is Callable for argument types ArgTypes and return type R if the expression INVOKE(f, declval<ArgTypes>()..., R), considered as an unevaluated operand (Clause 5), is well formed (20.8.2).

§20.8.2 [func.req]

p1 Define INVOKE(f, t1, t2, ..., tN) as follows:

  • (t1.*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is an object of type T or a reference to an object of type T or a reference to an object of a type derived from T;
  • ((*t1).*f)(t2, ..., tN) when f is a pointer to a member function of a class T and t1 is not one of the types described in the previous item;
  • [...]
  • f(t1, t2, ..., tN) in all other cases.

p2 Define INVOKE(f, t1, t2, ..., tN, R) as INVOKE(f, t1, t2, ..., tN) implicitly converted to R.

So, libc++ is certainly within its rights to do the SFINAE check in the templated ctor, since the types need to be complete, since otherwise you'd get undefined behaviour. However, it may be a bit unfortunate and be considered a defect that the safety check for a complete type triggers even if the actual SFINAE check is never needed (because the copy ctor will always be invoked). This may be alleviated by making the callable check a lazy one, like

template<bool Copy, class F>
struct lazy_callable{
static bool const value = callable<F>::value;
};

template<class F>
struct lazy_callable<true, F>{
static bool const value = false;
};

template<class F>
function(F f, typename enable_if<lazy_callable<!std::is_same<F,function>::value>::type* = 0);

This should only trigger the callable SFINAE check if F is not actually std::function<...>.

Man, I may have digressed a bit here at the end...



Related Topics



Leave a reply



Submit