Лучший способ использовать SFINAE для отключения не шаблонной функции-члена шаблона класса?

Есть много похожих вопросов, но я не могу найти именно этот: Учитывая

template <typename T>
struct S {
    int f(int x) const { return x; }
};

Я хочу иметь возможность отключать SFINAE f в зависимости от некоторой метафункции T. Очевидное не работает:

#include <type_traits>

template <typename T>
struct MyProperty { static constexpr auto value = std::is_same_v<T, float>; };

template <typename T>
struct S {
    template <typename = std::enable_if_t<MyProperty<T>::value>>
    int f(int x) const { return x; }
};

int main() {
    S<int> s;
}

Ближе всего я могу подойти к манекену typename U = T:

    template <typename U = T, typename = std::enable_if_t<MyProperty<U>::value>>
    int f(int x) const { 
        static_assert(std::is_same_v<T, U>, "Don't provide U.");
        return x;
    }

который работает https://godbolt.org/z/8qGs6P8Wd, но кажется окольным. Есть ли способ лучше?

Он плохо масштабируется, но вы можете специализировать сам класс. Это не СУХОЕ и ИМХО худшее решение, чем то, что у вас уже есть.

NathanOliver - Is on Strike 07.06.2023 22:38
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
1
78
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Если вы можете использовать C++20 или более позднюю версию, добавьте ограничение:

template<class T>
struct S {
    int f(int x) const requires MyProperty<T>::value { 
        return x;
    }
};

В C++17 вы могли бы сделать это так же, как сейчас, или переместить enable_if, чтобы он использовался для типа возвращаемого значения функции. Если вы также поместите условие std::is_same_v<U, T> в enable_if, а не в static_assert, это будет более удобно для SFINAE, если вы захотите включить другие f.

template<class T>
struct S {
    template<class U = T>
    std::enable_if_t<std::is_same_v<U, T> && MyProperty<U>::value, int> 
    f(int x) const { 
        return x;
    }
};

Другой вариант, который может быть полезен, если у вас есть много функций в вашем классе, которые вы хотели бы включить, только если MyProperty::value равно true, — это поместить все эти функции в базовый класс и условно наследовать от этого класса, используя CRTP.

struct empty {};

template<class T>
struct float_funcs {
    T& Self() { return *static_cast<T*>(this); }
    const T& Self() const { return *static_cast<const T*>(this); }

    // put all functions depending on the MyProperty trait being true here:
    int f(int x) const {
        return x + Self().foo; // `foo` is accessible since we're a friend
    }
};

template<class T> // added helper variable
inline constexpr bool MyProperty_v = MyProperty<T>::value;

// inherit from float_funcs<S<T>> if the condition is `true` or `empty` otherwise
template<class T>
struct S : std::conditional_t<MyProperty_v<T>, float_funcs<S<T>>, empty> {
private:
    friend std::conditional_t<MyProperty_v<T>, float_funcs<S<T>>, empty>;
    int foo = 1;
};

При этом вам не нужен SFINAE f, и вы получите явную ошибку компиляции, если попытаетесь использовать f, когда MyProperty<T> не выполняется. f в этом случае даже не существует ни в какой форме.

Лично я предпочитаю засовывать std::enable_if_t в template <...>, чтобы возвращаемый тип не скрывался в нем. Я выбрал подход static_assert(std::is_same_v<T, U>, "Don't provide U.");, потому что для кого-то будет серьезной ошибкой вызвать s.foo<float>(0);.

Ben 07.06.2023 23:11

@ Бен Конечно, это дело вкуса, куда поставить enable_if. Лично я предпочитаю не добавлять в функцию лишний фиктивный параметр шаблона, если это возможно. static_assert, безусловно, лучший вариант, если вам не нужно включать SFINAE для других f. Я просто поставил как вариант. Самая чистая версия C++20 точно. :-)

Ted Lyngmo 07.06.2023 23:14

Поскольку вы используете С++ 20, вы можете ограничить функцию:

template <typename T>
struct S {
    int f(int x) const requires(MyProperty<T>::value) { return x; }
};

В противном случае вы можете использовать наследование:

template <typename Self, typename T, typename = void>
struct MyPropertyS {};

template <typename Self, typename T>
struct MyPropertyS<Self, T, std::void_t<std::enable_if_t<MyProperty<T>::value>>> {
    // Access rest of class via `static_cast<Self&>(*this)`
    int f(int x) const { return x; }
};

template<typename T>
struct S : MyPropertyS<S<T>, T> {
    // Inherit f if it exists
};

(Или специализировать всю структуру S вместо того, чтобы наследовать условно измененную часть)

Это единственные способы, которыми вы можете действительно условно иметь функцию-член, не являющуюся шаблоном.

В противном случае ваш метод шаблона хорош. Вы также можете рассмотреть что-то вроде этого:

struct disabled {
    disabled() = delete;
    ~disabled() = delete;
};

template <typename T>
struct S {
    int f(std::conditional_t<MyProperty<T>::value, int, disabled> x) const { return x; }
};

который не избавится от f, но вы не сможете его вызвать, а s.f(0) будет дружественным к SFINAE сбоем (и его легко повторно использовать для нескольких функций с псевдонимом типа)

Другие вопросы по теме