включить оператор преобразования с помощью SFINAE

Я пытаюсь перегрузить operator T(), используя SFINAE, чтобы вернуть копию, когда T является фундаментальным типом, и константную ссылку, когда T является классом.

При использовании double в моем примере ниже я не могу удалить вторую перегрузку (с std::is_class).

То есть ошибка, которую я получаю:

error: no type named ‘type’ in ‘struct std::enable_if<false, const double&>’
operator typename std::enable_if< std::is_class<T>::value, const T&>::type () const
^

Что я делаю не так?

#include <iostream>
#include <type_traits>

template<typename T>
struct Foo
{
    operator typename std::enable_if<!std::is_class<T>::value, T >::type () const
    {
        return _val;
    }

    operator typename std::enable_if< std::is_class<T>::value, const T&>::type () const
    {
        return _val;
    }

    T _val;
};

int main()
{
    Foo<double> f1;
    f1._val = 0.3;

    double d = f1;
    std::cout << d << std::endl;
    return 0;
}

person Steve Lorimer    schedule 11.12.2014    source источник


Ответы (2)


T уже известно во время создания экземпляров ваших функций-членов класса, поэтому подстановка не происходит, и вместо SFINAE вы получаете серьезную ошибку. Самый простой обходной путь — ввести фиктивный параметр шаблона для этих перегруженных операторов и установить по умолчанию значение T, чтобы вывод типа все еще мог выполняться.

template<typename U = T>
operator typename std::enable_if<!std::is_class<U>::value, U >::type () const
{
    return _val;
}

template<typename U = T>
operator typename std::enable_if< std::is_class<U>::value, const U&>::type () const
{
    return _val;
}

Текущая демонстрация

person Praetorian    schedule 11.12.2014
comment
Немного другой пример и дополнительные сведения см. в разделе stackoverflow.com/questions/18100297/ - person Asher; 04.11.2015
comment
@Asher Использование SFINAE в аргументе шаблона по умолчанию работает в вопросе, на который вы ссылаетесь, но в вопросе выше, где OP пытается использовать его для определения двух взаимоисключающих перегрузок, он не будет как описано здесь. - person Praetorian; 04.11.2015
comment
Может ли это не позволить компилятору также создавать экземпляры нежелательных операторов преобразования типов? - person Museful; 06.05.2018
comment
@Museful Что ты имеешь в виду под нежелательным? Можете ли вы привести пример? - person Praetorian; 06.05.2018
comment
Возможно, мне следует спросить вот о чем: будет ли это точно эквивалентно использованию T вместо U в качестве второго параметра enable_if? (и const T& вместо const U&) - person Museful; 06.05.2018
comment
@Museful Да, это сработает, но опять же, что вы пытаетесь предотвратить? Это работает, потому что T=U. - person Praetorian; 07.05.2018
comment
@Praetorian Значит, T=U всегда будет правдой? Ничто (например, int i = Foo<double>() или что-то еще) не заставит компилятор прийти сюда с T!=U? - person Museful; 07.05.2018

Не решая проблему, почему неправильный оператор не был отброшен, для решения конкретной проблемы, то есть возврата по const ref для типов классов или по значению для других, решение может быть найдено с помощью std::conditional.

template< bool B, class T, class F >
struct conditional;

Предоставляет тип typedef члена, который определяется как T, если B имеет значение true во время компиляции, или как F, если B имеет значение false.

Рабочий пример:

#include <iostream>
#include <type_traits>

template<typename T>
struct Foo
{
    operator typename std::conditional<
        std::is_class<T>::value, const T&, T>::type () const
    {
        return _val;
    }

    T _val;
};

int main()
{
    Foo<double> f1;
    f1._val = 0.3;

    double d = f1;
    std::cout << d << std::endl;
    return 0;
}
person Steve Lorimer    schedule 11.12.2014
comment
Это кажется достойным способом избежать необходимости ответа на ваш вопрос, но стоит отметить, что его нельзя обобщить для работы, когда тела вашего оператора не идентичны на 100%. - person ; 12.12.2014
comment
@hvd Я согласен. Однако в этом конкретном случае, когда все, что я хочу сделать, это вернуть const ref для типов классов и по значению для других типов, я думаю, что использование std::conditional является более простым/коротким подходом, чем наличие 2 std_enable_if перегрузок. Вы согласны? - person Steve Lorimer; 12.12.2014
comment
Да, если тела двух операторов идентичны, я бы определенно выбрал решение std::conditional. - person Praetorian; 12.12.2014
comment
@SteveLorimer Конечно, я согласен, что это тоже улучшение. - person ; 12.12.2014
comment
По какой-то причине у меня возникли проблемы с версией перегрузки std::enable_if при попытке использовать арифметику через неявные преобразования (т.е. 3 + Foo / 2). Однако версия std::conditional работает отлично, а новая конструкция if constexpr теперь должна допускать неидентичные тела операторов. - person Joshua Little; 26.02.2020