2016-06-11 2 views
2

Использование g++ (Ubuntu 4.8.5-1ubuntu1) 4.8.5 и компиляции с g++ -std=c++11 -Wall -Wextra -Wconversionстанд :: функция неявное преобразование типа

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

template <typename T> 
struct Foo { 
    Foo(T t) {} 
}; 

struct Bar { 
    Bar(Foo<float> foo) : foo(foo) {} //Trying to convert Foo<float> to Foo<double> 
    Foo<double> foo; 
}; 

следующие компилирует с предупреждением от -Wconversion, как и ожидалось:

void foo(float t){} 
int main() { 
    foo(3.141592653589794626); 
    return 0; 
} 

Однако компилируется без предупреждений:

#include <functional> 

void foo(double t){} 

struct Bar { 
    Bar(std::function<void(float)> foo) : foo(foo) {} //Convert std::function<void(float)> to std::function<void(double)> 
    std::function<void(double)> foo; 
}; 

int main(){ 
    Bar bar(foo); //Convert std::function<void(double)> to std::function<void(float)> 
    bar.foo(3.141592653589794626); //Rounded to: 3.141592741012573 
    foo(3.141592653589794626);  //Not rounded: 3.141592653589794 
    return 0; 
} 

Очевидно, что это какое-то автоматическое преобразование float<->double но почему это позволило в третьем примере, а не первое? Почему -Wconversion не поймать это?

(Невидимая потеря точности является проблемой в ряде областей, например, при работе с широтой/долготой).

ответ

3

Как отметил Элвин Аренс, проблема с тип стирания происходит во внутренней работе std::function. Можно предположить, что быстрым решением было бы изменить тип аргумента конструктора на double, но это не мешает пользователю передавать функцию, которая принимает float. Например,

void foo(float t) { 
    std::cout << std::setprecision(15) << std::fixed << t << std::endl; 
} 

struct Bar { 
    Bar(std::function<void(double)> foo) : foo(foo) {} 
    std::function<void(double)> foo; 
}; 

int main() { 
    Bar bar(foo); 
    bar.foo(3.141592653589794626); //Rounded to: 3.141592741012573 
     foo(3.141592653589794626); //Not rounded: 3.141592653589794 
} 

компилирует файл, но дает нежелательный результат. Один исправить это, чтобы использовать конструктор шаблонов и некоторые TMP.

void foo(double t) { 
    std::cout << std::setprecision(15) << std::fixed << t << std::endl; 
} 

struct Bar { 
    using target_type = double; 
    using func_type = void(*)(target_type); 

    template <typename T, typename U = typename std::enable_if<std::is_same<T,func_type>::value,void>::type> 
    Bar(T foo) : foo(foo) {} 

    std::function<void(target_type)> foo; 
}; 

int main() { 
    Bar bar(foo); 
    bar.foo(3.141592653589794626); //Rounded to: 3.141592741012573 
     foo(3.141592653589794626); //Not rounded: 3.141592653589794 
} 

Теперь он не компилируется, если вы передаете в функцию, которая не совпадает с подписью Bar::foo. Усложнение состоит в том, что вы должны убедиться, что func_typeтакже соответствует подписи Bar::foo, если он когда-либо изменится.

+0

Спасибо Тим. После некоторого исследования ответа Эльвина Аренса я считаю, что в этом случае лучшим определением Bar является: 'template struct Bar { \t Бар (const F & foo): foo (foo) {} \t F &foo; }; '. Я не должен был использовать std :: function для начала! – lenguador

+0

@lenguador Будьте осторожны с этим решением. Если 'F' - это лямбда с временем жизни короче, чем созданная« Бар », вы попадете в плохие места. Я согласен с тем, что это решение обеспечивает лучшую безопасность типов, но также вызывает свернутые объявления: 'int meow (float); auto b = Bar (meow); ' – Tim

+0

Есть ли способ гарантировать, что функция предоставляется во время компиляции? В моем варианте использования функция известна во время компиляции, но я бы не хотел, чтобы кто-то ошибочно пропускал лямбду с неправильной продолжительностью жизни. – lenguador

2

Это связано с целью станд :: функции для того для выполнения полиморфизма и, следовательно, он использует тип стирания, которая была обсуждена herehere и here

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