Как я могу предотвратить неявные преобразования в std :: is_constructible - PullRequest
1 голос
/ 26 апреля 2020

Допустим, у меня есть несколько разных классов:

class constructible_from_float {
public: 
    constructible_from_float(float);
};
class constructible_from_double {
public: 
    constructible_from_double(double);
};
class constructible_from_long_double {
public: 
    constructible_from_long_double(long double);
};

И затем я хочу сделать что-то, основываясь на том, из какого типа они могут быть построены (упрощенный пример):

#include <type_traits>
template <typename T>
constexpr size_t foo() {
    if constexpr (std::is_constructible<T, float>::value) {
        return 1;
    } else if constexpr (std::is_constructible<T, double>::value) {
        return 2;
    } else if constexpr (std::is_constructible<T, long double>::value) {
        return 3;
    } else
        return -1;
}

Но проблема в том, что все они возвращают 1:

[[maybe_unused]] auto float_result = foo<constructible_from_float>();
[[maybe_unused]] auto double_result = foo<constructible_from_double>();
[[maybe_unused]] auto long_double_result = foo<constructible_from_long_double>();

enter image description here

Я знаю, что причина поведения неявна преобразования между типами. Существует ли способ git (используемый по крайней мере в трех основных компиляторах: msvc, gcc и clang), чтобы заставить компилятор различать guish между этими типами.

Мне не разрешено менять классы (constructible_from_float, et c.), Но я могу делать все остальное. Все, что предусмотрено стабильными версиями компиляторов, в порядке (включая c++2a).

1 Ответ

1 голос
/ 26 апреля 2020

Вы должны обмануть компилятор C ++, сообщив вам, какое неявное преобразование он хочет использовать, затем использовать SFINAE, чтобы вытащить коврик из-под ног, и не создать экземпляр шаблона, но SFINAE, так что это не ошибка.

#include <type_traits>
#include <iostream>

class constructible_from_float {
public:
    constructible_from_float(float);
};
class constructible_from_double {
public:
    constructible_from_double(double);
};
class constructible_from_long_double {
public:
    constructible_from_long_double(long double);
};


template<typename T> class convertible_only_to {

public:
    template<typename S, typename=std::enable_if_t<std::is_same_v<T,S>>>
    operator S() const
    {
        return S{};
    }
};


template <typename T>
constexpr int foo() {
    if constexpr (std::is_constructible<T,
              convertible_only_to<float>>::value) {
            return 1;
    } else
    if constexpr (std::is_constructible<T,
              convertible_only_to<double>>::value) {
            return 2;
        } else
    if constexpr (std::is_constructible<T,
              convertible_only_to<long double>>::value) {
            return 3;
    } else
        return -1;
}

struct not_constructible_from_anything {};

int main()
{
    std::cout << foo<constructible_from_float>() << std::endl;
    std::cout << foo<constructible_from_double>() << std::endl;
    std::cout << foo<constructible_from_long_double>() << std::endl;
    std::cout << foo<not_constructible_from_anything>() << std::endl;

    return 0;
}
...