Использование SFINAE для проверки существования функции std :: to_string для типа - PullRequest
2 голосов
/ 29 мая 2019

Я хочу создать шаблон функции, который будет специализирован для типов, к которым может применяться std::to_string, другой для классов, для которых я определил mylib::to_string, и позволить любым типам, для которых он может не перейти, к другие реализации или специализации.

Пример:

// I only want this to be found if std::to_string exists for ItemType
template<typename ItemType>
void func(ItemType &i)
{
     std::cout << std::to_string(i);
}

// I only want this to be found if mylib::to_string exists for ItemType
template<typename ItemType>
void func(ItemType &i)
{
     std::cout << mylib::to_string(i);
}

// And finally, I'd like to be able to fall through to anything that has a << defined for streams
template<>
void func<MyClass>(MyClass &i)
{
     std::cout << MySpecialConverterFunc(i);
}

У меня всегда проблемы с синтаксисом std::enable_if. Как бы вы добавили его в первый шаблон?

Ответы [ 2 ]

5 голосов
/ 29 мая 2019

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

Что касается самой проверки, вам действительно не нужно enable_if.SFINAE сделает лишь немного выражения:

template<typename ItemType>
auto func(ItemType &i) -> decltype(std::to_string(i), void())
{
     std::cout << std::to_string(i);
}

void func(MyClass &i)
{
     std::cout << MySpecialConverterFunc(i);
}

. В приведенном выше коде decltype применяется к выражению запятой.Левая часть - std::to_string(i), если оно не правильно сформировано, все выражение не правильно сформировано, и мы получаем ошибку подстановки, заставляя компилятор отбросить эту перегрузку (часть «не ошибка»).Если он правильно сформирован, правая часть - это литерал void(), поэтому decltype преобразуется в void, как вы и планировали.

Если замена не удалась, все, что у нас осталось, это вторая перегрузка,так что выбрано.В любом случае это было бы выбрано для этого случая, потому что не шаблонные перегрузки всегда предпочтительнее в разрешении перегрузки, если сигнатура одна и та же.Но добавление проверки для лучшего руководства компилятором в других случаях само по себе неплохая идея.

1 голос
/ 29 мая 2019

Избегайте SFINAE и пишите простой код, используя правило ADL

Вы можете избежать использования SFINAE, воспользовавшись ADL (Argument Dependent Lookup). ADL утверждает, что:

Если функция и тип объявлены в одном и том же пространстве имен, вам не нужно указывать пространство имен при вызове этой функции.

Например:

namespace foo 
{
    class MyClass {};
    std::string to_string(MyClass const& x) {
        return "[Element of MyClass]"; 
    } 
}

int main() {
    foo::MyClass x;

    std::cout << to_string(x); // This is legal because x is in namespace foo
}

Если вы пишете to_string функции в вашем пространстве имен для ваших типов, то вы можете написать func один раз в следующем выражении:

template<class T>
void func(T const& t) {
    using std::to_string; 
    // This calls std::to_string if std::to_string is defined;
    // Otherwise it uses ADL to find the correct to_string function
    std::cout << to_string(t); 
}

В этом примере func можно вызвать с помощью foo::MyClass, потому что есть функция to_string, которая принимает MyClass в пространстве имен foo:

int main() {
    func(10); // Prints 10; 
    func(3.1415); // Prints 3.1415

    foo::MyClass x;
    func(x); // Prints [Element of MyClass]
}

А как насчет типов в пространствах имен, которые мне не принадлежат?

В этом случае я бы порекомендовал создать дополнительное пространство имен и поместить туда функции to_string, а затем добавить его как оператор using:

namespace textConvert {
    // Because we use std::to_string here, it automatically gets included
    // When we use textConvert::to_string
    using std::to_string; 

    // Convert a vector
    template<class T>
    std::string to_string(std::vector<T> const& vect) {
        using std::to_string; 

        std::string str = "[";        
        for(auto& elem : vect) {
            str += to_string(elem);
            str += ','; 
        }
        str.back() = ']';
        return str; 
    }
}

Затем мы можем обновить func, чтобы включить textConvert::to_string, и поскольку textConvert использует std::to_string в дополнение к различным настраиваемым функциям преобразования, оба включаются!

template<class T>
void func(T const& t) {
    // This *also* automatically includes std::to_string
    // Because we put using std::to_string inside textConvert
    using textConvert::to_string;

    std::cout << to_string(t);
}
...