英文:
Best way to use SFINAE to disable an otherwise-non-templated member function of a class template?
问题
以下是您提供的代码的翻译:
有许多看起来类似的问题,但我找不到完全相同的问题:给定
template <typename T>
struct S {
int f(int x) const { return x; }
};
我希望能够根据T
的某些元函数SFINAE掉f
。显而易见的方法不起作用:
#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>, "不要提供 U.");
return x;
}
这个方法在 https://godbolt.org/z/8qGs6P8Wd 上可以工作,但感觉有点绕。是否有更好的方法?
<details>
<summary>英文:</summary>
There are many similar-looking questions, but I can't find quite this one: Given
```cpp
template <typename T>
struct S {
int f(int x) const { return x; }
};
I want to be able to SFINAE-away f
depending on some metafunction of T
. The obvious thing doesn't work:
#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;
}
The closest I can come is a dummy 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;
}
which works https://godbolt.org/z/8qGs6P8Wd but feels round-about. Is there a better way?
答案1
得分: 7
If you can use C++20 or later, add a constraint:
template<class T>
struct S {
int f(int x) const requires MyProperty<T>::value {
return x;
}
};
In C++17, you could do it like you do it now or move the enable_if
to be used for the function's return type. If you put the condition std::is_same_v<U, T>
in the enable_if
too instead of in a static_assert
, it'll be more SFINAE friendly in case you want to enable other f
s.
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;
}
};
Another option that can be useful if you have many functions in your class that you'd like to enable only if MyProperty::value
is true
is to put all those functions in a base class and inherit from that class conditionally using 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;
};
With this, you don't need SFINAE away f
, and you'll get a clear compilation error if you try to use f
when MyProperty<T>
is not fulfilled. f
doesn't even exist in any form in that case.
英文:
If you can use C++20 or later, add a constraint:
template<class T>
struct S {
int f(int x) const requires MyProperty<T>::value {
return x;
}
};
In C++17, you could do it like you do it now or move the enable_if
to be used for the function's return type. If you put the condition std::is_same_v<U, T>
in the enable_if
too instead of in a static_assert
, it'll be more SFINAE friendly in case you want to enable other f
s.
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;
}
};
Another option that can be useful if you have many functions in your class that you'd like to enable only if MyProperty::value
is true
is to put all those functions in a base class and inherit from that class conditionally using 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;
};
With this you don't need SFINAE away f
and you'll get a clear compilation error if you try to use f
when MyProperty<T>
is not fulfilled. f
doesn't even exist in any form in that case.
答案2
得分: 1
由于您正在使用C++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>>> {
// 通过 `static_cast<Self&>(*this)` 访问类的其余部分
int f(int x) const { return x; }
};
template<typename T>
struct S : MyPropertyS<S<T>, T> {
// 如果存在,则继承f
};
(或者专门为整个结构体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友好的失败(并且很容易为具有类型别名的多个函数重复使用)。
英文:
Since you are using C++20, you can constrain the function:
template <typename T>
struct S {
int f(int x) const requires(MyProperty<T>::value) { return x; }
};
Otherwise you can use inheritance:
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
};
(Or specialize the whole struct S
itself instead of inheriting a part that is conditionally changed)
These are the only ways you can truly conditionally have a non-template member function.
Otherwise, your template method is good. You might also want to consider something like this:
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; }
};
which won't get rid of f
, but you won't be able to call it and s.f(0)
will be a SFINAE friendly fail (and it's easy to reuse for multiple functions with a type alias)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论