Std:: function в качестве параметра шаблона

В настоящее время у меня есть map<int, std::wstring>, но для гибкости я хочу иметь возможность назначать лямбда-выражение, возвращая std::wstring в качестве значения на карте.

Итак, я создал этот класс шаблонов:

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;
    }
};

и используйте его как:

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"

Теперь я не хочу использовать конструктор для упаковки лямбда, поэтому я решил добавить второй оператор присваивания, на этот раз для std::function:

ValueOrFunction& operator= (const std::function<T()>& other)
{
    m_func = other;
    return *this;
}

Это тот момент, когда компилятор начинает жаловаться. Строка mymap[124] = L"blablabla"; неожиданно приводит к этой ошибке:

ошибка C2593: 'operator = is неоднозначно'

IntelliSense дает дополнительную информацию:

более чем один оператор "=" соответствует этим операндам: function "ValueOrFunction:: operator = (const std:: function & other) [с T = std:: wstring]" function "ValueOrFunction:: operator = (const T & other) [with T = std:: wstring]" Типы операндов: ConfigurationValue = const wchar_t [10] c:\projects\beta\CppTest\CppTest\CppTest.cpp 37 13 CppTest

Итак, мой вопрос: почему компилятор не может отличить std::function<T()> и T? И как я могу это исправить?

Ответы

Ответ 1

Основная проблема заключается в том, что std::function имеет жадный неявный конструктор, который будет пытаться преобразовать что-либо и не сможет скомпилироваться в теле. Поэтому, если вы хотите перегрузить его, вам не разрешено никакое преобразование в альтернативу, вам нужно отключить материал, который может преобразовать альтернативу из вызова перегрузки std::function.

Самый простой способ - отправка тегов. Сделайте operator= жадным и настроенным для идеальной пересылки, затем вручную отправьте в метод assign с тегом:

 template<typename U>
 void operator=(U&&u){
   assign(std::forward<U>(u), std::is_convertible<U, std::wstring>());
 }
 void assign(std::wstring, std::true_type /*assign_to_string*/);
 void assign(std::function<blah>, std::false_type /*assign_to_non_string*/);

в основном мы делаем ручное разрешение перегрузки.

Более продвинутые методы: (возможно, не нужны)

Другим подходом было бы ограничение std::function = на SFINAE на вызываемом аргументе, но это бесполезно.

Если у вас несколько разных типов, конкурирующих с вашим std::function, вам приходится грустно вручную отправлять все из них. Способ исправления заключается в том, чтобы проверить, не является ли ваш тип U недействительным, а результат конвертируется в T, а затем отправляет тег на него. Придерживайте перегруженность не std::function в альтернативной ветки, и пусть обычная более традиционная перегрузка произойдет для всего остального.

Существует тонкая разница в том, что тип, конвертируемый как в std::wstring, так и вызываемый, возвращающий что-то конвертируемое в T, заканчивается отправкой на разные перегрузки, чем исходное простое решение выше, потому что используемые тесты не являются фактически взаимоисключающими, Для полной ручной эмуляции перегрузки С++ (исправлено для глупости std::function) вам нужно сделать этот случай неоднозначным!

Последнее, что нужно сделать, - использовать auto и возвращать типы возвращаемых данных, чтобы повысить способность другого кода определять, действительно ли ваш =. Лично я бы этого не делал до С++ 14, кроме как под принуждением, если бы я не писал серьезный библиотечный код.

Ответ 2

Оба std::function и std::wstring имеют операторы преобразования, которые могут принимать буквенную широкую строку, которую вы передаете. В обоих случаях конверсии определяются пользователем, и, следовательно, последовательность преобразования имеет тот же приоритет, что вызывает неоднозначность. Это основная причина ошибки.