Есть много похожих вопросов, но я не могу найти именно этот: Учитывая
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, но кажется окольным. Есть ли способ лучше?





Если вы можете использовать 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);.
@ Бен Конечно, это дело вкуса, куда поставить enable_if. Лично я предпочитаю не добавлять в функцию лишний фиктивный параметр шаблона, если это возможно. static_assert, безусловно, лучший вариант, если вам не нужно включать SFINAE для других f. Я просто поставил как вариант. Самая чистая версия C++20 точно. :-)
Поскольку вы используете С++ 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 сбоем (и его легко повторно использовать для нескольких функций с псевдонимом типа)
Он плохо масштабируется, но вы можете специализировать сам класс. Это не СУХОЕ и ИМХО худшее решение, чем то, что у вас уже есть.