14

Consider the following case:

void Set(const std::function<void(int)> &fn);
void Set(const std::function<void(int, int)> &fn);

Now calling the function with

Set([](int a) {
    //...
});

Gives "ambiguous call to overloaded function" error. I am using Visual Studio 2010. Is there a work around or another method to achieve something similar. I cannot use templates, because these functions are stored for later use because I cannot determine the number of parameters in that case. If you ask I can submit more details.

Cem Kalyoncu
  • 14,120
  • 4
  • 40
  • 62
  • 2
    Please explain "I cannot use templates, because the functions are stored for later use." As far as I know, there is nothing preventing the storage of an object whose type is specified by a template parameter. – Michael Price Nov 08 '11 at 16:25
  • 1
    @MichaelPrice: Thinking about it, its not much related with storage, but, if I use a template type for the function I will never know the number of parameters that I can pass to it. – Cem Kalyoncu Nov 08 '11 at 16:31
  • 3
    A better context for this question is, _"Why is this call ambiguous when using a lambda?"_, _"Is there a work around?"_. – deft_code Nov 08 '11 at 16:42
  • @deft: The code is ambiguous no matter what you pass. Let me find the duplicate I answered a whole while ago. – Xeo Nov 08 '11 at 17:16
  • 1
    possible duplicate of [Isn't the template argument (the signature) of std::function part of its type?](http://stackoverflow.com/questions/5931214/isnt-the-template-argument-the-signature-of-stdfunction-part-of-its-type) – Xeo Nov 08 '11 at 17:17
  • 1
    @Xeo: I am asking how to solve it, not why it is like that. Questions are linked but not duplicates. – Cem Kalyoncu Nov 08 '11 at 17:28
  • @CemKalyoncu: Read [@Xeo's answer](http://stackoverflow.com/questions/5931214/isnt-the-template-argument-the-signature-of-stdfunction-part-of-its-type/5931334#5931334) to the duplicate question. He gives 3 possible work arounds for ambiguous call. – deft_code Nov 08 '11 at 18:28
  • @deft_code: Please read those three answers, they are all based on explicit casting. I cannot accept it as answer. Check Nawaz's answer, thats what an answer is. – Cem Kalyoncu Nov 08 '11 at 18:44
  • @Cem: My linked example in my answer is not as verbose and solves the problem too. Also, like I said in my answer, I did consider SFINAE but discarded it because it's more verbose than my linked version. – Xeo Nov 08 '11 at 20:21
  • @Xeo: Yes I read that part. Your third answer comes close but still it won't work with lambdas. I don't have any problem with normal and member functions. Lambdas was added as additional option and their variants creates problems. Sorry if I was a little harsh before. I was pretty angry about VS and variadic templates. – Cem Kalyoncu Nov 08 '11 at 22:16

3 Answers3

12

I would suggest this solution. It should work with lambdas as well as with function-objects. It can be extended to make it work for function pointer as well (just go through the link provided at the bottom)

Framework:

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
{
    enum { arity = sizeof...(Args) };
};

template<typename Functor, size_t NArgs>
struct count_arg : std::enable_if<function_traits<Functor>::arity==NArgs, int>
{};

Usage:

template<typename Functor>
typename count_arg<Functor, 1>::type Set(Functor f) 
{
    std::function<void(int)> fn = f;
    std::cout << "f with one argument" << std::endl;
}

template<typename Functor>
typename count_arg<Functor, 2>::type Set(Functor f)
{
    std::function<void(int, int)> fn = f;
    std::cout << "f with two arguments" << std::endl;
}

int main() {
        Set([](int a){});
        Set([](int a, int b){});
        return 0;
}

Output:

f with one argument
f with two arguments

I took some help from the accepted answer of this topic:


Work around for Visual Studio 2010

Since Microsoft Visual Studio 2010 doesn't support variadic templates, then the framework-part can be implemented as:

template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())>
{};

template <typename C, typename R, typename T0>
struct function_traits<R(C::*)(T0) const> { enum { arity = 1 }; };

template <typename C, typename R, typename T0, typename T1>
struct function_traits<R(C::*)(T0,T1) const> { enum { arity = 2 }; };

template <typename C, typename R, typename T0, typename T1, typename T2>
struct function_traits<R(C::*)(T0,T1,T2) const> { enum { arity = 3 }; };

//this is same as before 
template<typename Functor, size_t NArgs, typename ReturnType=void>
struct count_arg : std::enable_if<function_traits<Functor>::arity==NArgs, ReturnType>
{};

EDIT
Now this code supports any return type.

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • Looks perfect, I will be trying in a couple of hours. I hope it will work with Visual Studio 2010. – Cem Kalyoncu Nov 08 '11 at 17:14
  • @CemKalyoncu: Edited and improved! – Nawaz Nov 08 '11 at 17:19
  • Sadly it doesn't work in VS 2010, my hopes are with the next version of it. I will accept the answer since it solves the issue in more compliant compilers. I hope Visual Studio will not turn into Internet Explorer. – Cem Kalyoncu Nov 08 '11 at 18:22
  • @CemKalyoncu: VCNext won't have varadic templates either. – Nicol Bolas Nov 08 '11 at 18:30
  • @NicolBolas: I just read it. It is now officially IE like. I was so happy when MS released VS2005 which was more or less standard complying compiler. – Cem Kalyoncu Nov 08 '11 at 18:38
  • @CemKalyoncu: See the workaround for MSVS20100. I just added it to my answer. – Nawaz Nov 08 '11 at 18:46
  • 1
    Thanks a lot, you fix works. I was trying to get something similar using macros (like in VS own code, I was digging how they managed functional without variadic templates) but your way is much easier. – Cem Kalyoncu Nov 08 '11 at 18:55
  • @Nawaz: I have edited your code and post a bit to support any return type. – Cem Kalyoncu Nov 08 '11 at 22:24
1

I suggest:

  void Set(void(*f)(int, int))
  { 
      std::function<void(int,int)> wrap(f);
      // ...
  }

  void Set(void(*f)(int))
  { 
      std::function<void(int)> wrap(f);
      // ...
  }
sehe
  • 374,641
  • 47
  • 450
  • 633
  • It doesn't work with lambdas in VS 2010. They cannot be used as function pointers even without capturing. – Cem Kalyoncu Nov 08 '11 at 17:06
  • @CemKalyoncu: that's surprising. Perhaps you'd need to sepcify the return type (`[](int a) -> void { ... }`) – sehe Nov 08 '11 at 17:17
  • 2
    @sehe: No, the problem is that the automatic conversion came into the standard too late for VS2010. – Xeo Nov 08 '11 at 17:25
1

You can manually specify the type:

Set(std::function<void(int)>([](int a) {
    //...
}));
Daniel
  • 30,896
  • 18
  • 85
  • 139
  • I know that works, but I'm trying to avoid it. I can also rename the functions. – Cem Kalyoncu Nov 08 '11 at 17:03
  • @CemKalyoncu: if you can rename the functions you can just use Set1 with 1 parameter and Set2 with 2 parameters? – Daniel Nov 08 '11 at 17:07
  • The last resort will be that. There are actually four more Set functions which works fine (two for normal function pointers, two for class member functions). I'm now trying to expand it, and the problem is to support lambdas. – Cem Kalyoncu Nov 08 '11 at 17:10