5

I'm working on Multi-type map holder. It works with all primitive types and also with structs e.g. Point. However, if I want to add std::function as another supported type (used for callbacks) then the compiler complains:

MT.cpp:426:15: No viable overloaded '='

MT.h:31:7: Candidate function (the implicit copy assignment operator) not viable: no known conversion from '(lambda at MT.cpp:426:17)' to 'const sharkLib::MT' for 1st argument

MT.h:31:7: Candidate function (the implicit move assignment operator) not viable: no known conversion from '(lambda at MT.cpp:426:17)' to 'sharkLib::MT' for 1st argument

I don't actually overload = operator but instead overload [] with dedicated constructor per supported type.

.h

protected: 
    map<string,MT> valueMap;

public:
    MT (int value);
    MT (std::function<void(Ref*)> ccb);
    virtual MT& operator[] (const char* key);

.cpp

MT::MT (int value)
{
    this->type = ValueType::intValue;
    this->value.int_ = value;
}

MT::MT (std::function<void(Ref*)> value)
{
    this->type = ValueType::ccbValue;
    this->value.ccb_ = value;
}

MT& MT::operator[] (const char* key)
{
    return this->valueMap[key];
}

usage

MT mt;

mt["int"] = 1;
mt["ccb"] = [](Ref *){ CCLOG("Pressed"); };

This last line is the one with error.

PerfectGamesOnline.com
  • 1,774
  • 1
  • 18
  • 31

2 Answers2

4

The problem is that you are trying to use a double conversion sequence:

  1. From lambda function to std::function<void(Ref*)>
  2. From std::function<void(Ref*)> to MT

On way around that is to remove the need of the double conversion, using either

mt["cast via function"] = static_cast<std::function<void(Ref*)>([](Ref*){ /*...*/ });
mt["cast via MT"] = MT([](Ref*){ /*...*/ });

If you want to support the conversion from a function type to MT you'd need a constructor of MT which directly takes the function type. Assuming none of your other constructors is written with using an unconstrained template, you could just add

template <typename Fun>
MT::MT(Fun&& fun)
    : type(ValueType::ccbValue) {
    this->value.ccb = std::forward<Fun>(fun);
}

If you are already using an unconstrained template for another type you'd need to use suitable conditions, e.g. std::is_convertible<Fun, std::function<void(Ref*)>>::value, together with a suitable SFINAE-approach to remove the respective constructor(s) from the overload set.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • Why not include the sfinae line in your constructor explicitly? – Yakk - Adam Nevraumont Aug 29 '16 at 00:26
  • @Yakk: if the constructor is the only one with an unconstrained template it is unnecessary and if it is not it is insufficient as the other constructor would need to be handled with the opposite case. So it seems pointless including it. – Dietmar Kühl Aug 29 '16 at 00:30
  • 1
    `MT const&&` and `MT&` would prefer `Fun&&` to `MT&&` or `MT const&` no? Plus, better error messages. [live example](http://coliru.stacked-crooked.com/a/add8adee5cf29d93). Basically you should never have a non-SFINAE protected one-argument perfect forwarding ctor. – Yakk - Adam Nevraumont Aug 29 '16 at 01:00
2

Ok, Chris inspired me and this is the solution:

typedef std::function<void(Ref*)> ClickCallback;
...
    MT (ClickCallback ccb);
...
mt["ccb"] = (ClickCallback) [](Ref *){ CCLOG("Pressed "); };;
PerfectGamesOnline.com
  • 1,774
  • 1
  • 18
  • 31