2016-02-08 4 views
3

Почему этот конструктор называется двусмысленным?Почему этот вызов перегруженной функции неоднозначен?

#include <functional> 

class A { 
    std::function<int(void)> f_; 
    std::function<float(void)> g_; 
public: 
    A(std::function<int(void)> f) { f_ = f; } 
    A(std::function<float(void)> g) { g_ = g; } 
    ~A() {} 
}; 

int main() 
{ 
    A a([](){ return (int)1; }); 
    return 0; 
} 

Обратите внимание на тип.

Есть ли способ рассказать компилятору, какой конструктор перегружен для использования?

+0

Но это работает, когда я использую другой тип возвращаемого значения для шаблона функции (например, тип указателя) – mak

+0

Связанные: HTTP: //переполнение стека.com/questions/22542780/c11-auto-stdfunction-and-twoiguous-call-to-overloaded-function – 101010

+0

Что вы пытаетесь сделать? Возможно, начиная с этого было бы лучше здесь. Например, вы можете сохранить как '[]() -> int', так и' []() -> float' в 'function ()>'. Затем, вызывая его, вы можете получить результат с помощью 'boost :: apply_visitor (a.f(), [] (auto x) {...})' –

ответ

3

Потому что то, что вы проходите, не соответствует типам, поэтому мы входим в последовательности преобразования, чтобы найти перегрузку для использования. Обе версии функции могут быть неявно созданы из лямбда-объекта, который возвращает int. Таким образом, компилятор не может решить, что выбрать для создания; хотя кажется интуитивно очевидным, что правила на C++ не позволяют этого.

Edit:

Написано экспромтом, но я думаю, что это может сделать трюк:

template < typename Fun > 
typename std::enable_if<std::is_same<typename std::result_of<Fun()>::type, int>::value>::type f(Fun f) ... 


template < typename Fun > 
typename std::enable_if<std::is_same<typename std::result_of<Fun()>::type, double>::value>::type f(Fun f) ... 

и т.д ... Или вы можете использовать тег диспетчеризацию:

template < typename Fun, typename Tag > 
struct caller; 

template < typename T > tag {}; 

template < typename Fun > 
struct caller<Fun, tag<int>> { static void call(Fun f) { f(); } }; 

// etc... 

template < typename Fun > 
void f(Fun fun) { caller<Fun, typename std::result_of<Fun()>>::call(fun); } 
+0

Есть ли еще одна оболочка, чем функция для работы с лямбдой? Тот, у которого есть менее жадный конструктор шаблонов? – mak

+0

Возможно, вы можете создать его с помощью SFINAE. Для использования на лямбдах может потребоваться некоторые навыки метапрограммирования помимо основного, особенно если вы хотите принять бесплатные функции и членов. –

+0

Я узнал, что он работает с указателями функций стиля c, такими как 'A (int (* f) (void));'. К сожалению, похоже, что они не совместимы с функциями захвата лямбда. И мои навыки метапрограммирования не настолько продвинуты, чтобы понять эту странную вещь. – mak

3

Это дефект в стандарте. См DR 2132:

Рассмотрим следующий пример:

#include <functional> 

void f(std::function<void()>) {} 
void f(std::function<void(int)>) {} 

int main() { 
    f([]{}); 
    f([](int){}); 
} 

Призывы е в магистральный неоднозначны. По-видимому, потому, что конверсионные последовательности до std::function от лямбда составляют . Стандарт указывает, что объект функции, присвоенный std::function ", должен быть обратимся (20.8.11.2) для типов аргументов ArgTypes и типа возврата R." Он не говорит, что если это не случай, конструктор не входит в набор перегрузки.

Попробуйте использовать указатель на функцию в качестве аргумента вместо:

A(int f()) { f_ = f; } 
A(float g()) { g_ = g; } 
+0

Как я написал в другом комментарии, аргументы указателя функции несовместимы с функциональностью захвата лямбда. По какой-то причине я получаю ошибку 'un matching function for call to ... 'при использовании указателей функций и переменных захвата. – mak

+1

DR2132 об удалении неявной последовательности преобразований из объектов функций, не вызываемых с указанной сигнатурой. В случае OP lambda может быть выведена для обеих подписей ('int()' и 'float()'). –

0
template<class F, 
    class R=std::result_of_t<F&()>, 
    std::enable_if_t<std::is_convertible<R,int>{}&&(!std::is_convertible<R,float>{}||std::is_integral<R>{}),int> =0 
> 
A(F&&f):A(std::function<int()>(std::forward<F>(f))){} 
template<class F, 
    class R=std::result_of_t<F&()>, 
    std::enable_if_t<std::is_convertible<R,float>{}&&(!std::is_convertible<R,int>{}||std::is_floating_point<R>{}, int> =0 
> 
A(F&&f):A(std::function<float()>(std::forward<F>(f))){} 
A(std::function<int()> f) { f_ = f; } 
A(std::function<float()> g) { g_ = g; } 

Здесь мы неоднозначные случаи, и отправку интегральных к перегрузке INT, и не плавающий к поплавку один.

Случаи, когда они конвертируются в оба, но ни с плавающей точкой, ни с интегралом, остаются двусмысленными. Как и должно быть.

Предложение sfina может быть сложным. Если он не работает, замените:

std::enable_if_t<std::is_convertible<R,float>{}&&(!std::is_convertible<R,int>{}||std::is_floating_point<R>{}, int> =0 

С

class=std::enable_if_t<std::is_convertible<R,float>{}&&(!std::is_convertible<R,int>{}||std::is_floating_point<R>{}> 

и аналогичные для других CTOR. Это может работать в MSVC например.

Полный по тегу диспетчеризации может потребоваться для MSVC, из-за отсутствие экспрессии SFINAE в 2015 году

Смежные вопросы