I currently have a map<int, std::wstring>
, but for flexibility, I want to be able to assign a lambda expression, returning std::wstring
as value in the map.
So I created this template class:
template <typename T>
class ValueOrFunction
{
private:
std::function<T()> m_func;
public:
ValueOrFunction() : m_func(std::function<T()>()) {}
ValueOrFunction(std::function<T()> func) : m_func(func) {}
T operator()() { return m_func(); }
ValueOrFunction& operator= (const T& other)
{
m_func = [other]() -> T { return other; };
return *this;
}
};
and use it like:
typedef ValueOrFunction<std::wstring> ConfigurationValue;
std::map<int, ConfigurationValue> mymap;
mymap[123] = ConfigurationValue([]() -> std::wstring { return L"test"; });
mymap[124] = L"blablabla";
std::wcout << mymap[123]().c_str() << std::endl; // outputs "test"
std::wcout << mymap[124]().c_str() << std::endl; // outputs "blablabla"
Now, I don't want to use the constructor for wrapping the lambda, so I decided to add a second assignment operator, this time for the std::function
:
ValueOrFunction& operator= (const std::function<T()>& other)
{
m_func = other;
return *this;
}
This is the point where the compiler starts complaining. The line mymap[124] = L"blablabla";
suddenly results in this error:
error C2593: 'operator = is ambiguous'
IntelliSense gives some more info:
more than one operator "=" matches these operands: function "ValueOrFunction::operator=(const std::function &other) [with T=std::wstring]" function "ValueOrFunction::operator=(const T &other) [with T=std::wstring]" operand types are: ConfigurationValue = const wchar_t [10] c:\projects\beta\CppTest\CppTest\CppTest.cpp 37 13 CppTest
So, my question is, why isn't the compiler able to distinguish between std::function<T()>
and T
? And how can I fix this?
The basic problem is that
std::function
has a greedy implicit constructor that will attempt to convert anything, and only fail to compile in the body. So if you want to overload with it, either no conversion to the alternative can be allowed, of you need to disable stuff that can convert to the alternative from calling thestd::function
overload.The easiest technique would be tag dispatching. Make an
operator=
that is greedy and set up for perfect forwarding, then manually dispatch to anassign
method with a tag:basically we are doing manual overload resolution.
More advanced techniques: (probably not needed)
Another approach would be to limit the
std::function
=
with SFINAE on the argument being invoked is valid, but that is messier.If you have multiple different types competing with your
std::function
you have to sadly manually dispatch all of them. The way to fix that is to test if your typeU
is callable with nothing and the result convertible toT
, then tag dispatch on that. Stick the non-std::function
overloads in the alternative branch, and let usual more traditional overloading to occur for everything else.There is a subtle difference in that a type convertible to both
std::wstring
and callable returning something convertible toT
ends up being dispatched to different overloads than the original simple solution above, because the tests used are not actually mutually exclusive. For full manual emulation of C++ overloading (corrected forstd::function
s stupidity) you need to make that case ambiguous!The last advanced thing to do would be to use
auto
and trailing return types to improve the ability of other code to detect if your=
is valid. Personally, I would not do this before C++14 except under duress, unless I was writing some serious library code.