1

I am still struggling with some C++ syntax.
This time I want to add extra arguments with the lambda. But to make the code generic I ant to be able to accept any function and its arguments:

#include <functional>
#include <exception> 

template<typename R>
class Nisse
{
    private:
        Nisse(Nisse const&)             = delete;
        Nisse(Nisse&&)                  = delete;
        Nisse& operator=(Nisse const&)  = delete;
        Nisse& operator=(Nisse&&)       = delete;
    public:
        //Nisse(std::function<R()> const& func) {}  // disable for testing

        template<typename... Args>
        Nisse(std::function<R(Args...)> const& func, Args... a) {}
};

int main()
{
    // I  was hoping this would deduce the template arguments.
    Nisse<int>   nisse([](int a,double d){return 5;},12,12.0);
}

This generates:

> g++ -std=c++0x Test.cpp 
Test.cpp:21:61: error: no matching function for call to ‘Nisse<int>::Nisse(main()::<lambda(int, double)>, int, double)’
Test.cpp:21:61: note: candidate is:
Test.cpp:16:9: note: template<class ... Args> Nisse::Nisse(const std::function<R(Args ...)>&, Args ...)

I tried explicitly specifying the template types:

    Nisse<int>   nisse<int,double>([](int a,double d){return 5;},12,12.0);

But this (surprising to me) is a syntax error:

> g++ -std=c++0x Test.cpp 
Test.cpp: In function ‘int main()’:
Test.cpp:21:23: error: expected initializer before ‘<’ token
Test.cpp:21:65: error: expected primary-expression before ‘,’ token
Test.cpp:21:73: error: expected ‘;’ before ‘)’ token
Xeo
  • 129,499
  • 52
  • 291
  • 397
Martin York
  • 257,169
  • 86
  • 333
  • 562
  • 1
    If you want to deduce the `Args...` pack from the `Args... args` parameters, [this answer](http://stackoverflow.com/a/11774533/500104) might help you. If you expect those deduced from the `std::function`, you're [out of luck](http://stackoverflow.com/a/14784584/500104). Also, I generally recommend to just accept `F f, Args... args`. – Xeo Feb 14 '13 at 17:58
  • Note that I changed my comment, you might want to reread it. – Xeo Feb 14 '13 at 18:00

1 Answers1

4

You can't infer std::function template arguments from a lambda. The usual way to accept arbitrary callables is by universal reference:

    template<typename F, typename... Args,
      typename = typename std::enable_if<std::is_convertible<
        decltype(std::declval<F>()(std::declval<Args>()...)), R>::value>::type>
    Nisse(F &&f, Args... a): Nisse(std::function<R()>(std::bind(f, a...))) {}

The final (anonymous, defaulted) template argument is used here to validate that the first template argument is function like and returns a result of type R.

std::enable_if<std::is_convertible<
    decltype(std::declval<F>()(std::declval<Args>()...)), R>::value>::type

As described in the comments below by @Yakk The above expression checks that F is a function type whose result is R. If it works then all is fine. If it fails then it generates a compile time error (NOTE: this uses SFINAE).

For methods, SFINAE is inserted into the return type, but for constructors this isn't an option; historically an extra, default constructor argument was added but adding a defaulted template argument is more elegant as it doesn't change the constructor signature at all. SFINAE by anonymous template parameter is particularly attractive following variadic template parameters as there's no way for the user to (accidentally?) override the default.

ecatmur
  • 152,476
  • 27
  • 293
  • 366
  • 1
    I'd use `is_convertible` instead of `is_same`. Also, `std::result_of` isn't SFINAE-safe and will trigger a hard error if you pass something un-function-like. – Xeo Feb 14 '13 at 18:01
  • @Xeo `decltype( std::declval()(std::declval)()...) )` instead of `std::result_of<...>::type` will fix that? (I assume `result_of` isn't SFINAE-safe because the default `std::result_of` is undefined rather than empty? Strange design decision if so.) – Yakk - Adam Nevraumont Feb 14 '13 at 19:07
  • Can you explain the third template argument or a link to explanation. – Martin York Feb 14 '13 at 19:20
  • @Yakk: That might work, but I'm not wholly sure - better write your own `result_of` with an empty base definition. – Xeo Feb 14 '13 at 19:25
  • @LokiAstari SFINAE used in a default parameter. The goal is to create an expression that will generate a substitution failure on and only on invalid types. It is an anonymous 3rd argument whose type will be `void` (that is what `enable_if<...>::type` is by default) if and only if `F` can be evaluated with `a...` and returns `R`. As noted by @Xeo, convertable-to-`R` would be a better criteria, and the above has a bug in that `std::result_of` isn't SFINAE compatible. The short version of all of this? You cannot be told about SFINAE, you can only experience it. – Yakk - Adam Nevraumont Feb 14 '13 at 19:43
  • 1
    @LokiAstari [This article](http://flamingdangerzone.com/cxx11/2012/06/01/almost-static-if.html) also explains the use of `enable_if` (and gives an alternative). – Luc Danton Feb 15 '13 at 11:17