使用SFINAE的最佳方式来禁用类模板的一个否则非模板成员函数是什么?

huangapple go评论83阅读模式
英文:

Best way to use SFINAE to disable an otherwise-non-templated member function of a class template?

问题

以下是您提供的代码的翻译:

  1. 有许多看起来类似的问题,但我找不到完全相同的问题:给定
  2. template <typename T>
  3. struct S {
  4. int f(int x) const { return x; }
  5. };

我希望能够根据T的某些元函数SFINAE掉f。显而易见的方法不起作用:

  1. #include <type_traits>
  2. template <typename T>
  3. struct MyProperty { static constexpr auto value = std::is_same_v<T, float>; };
  4. template <typename T>
  5. struct S {
  6. template <typename = std::enable_if_t<MyProperty<T>::value>>
  7. int f(int x) const { return x; }
  8. };
  9. int main() {
  10. S<int> s;
  11. }

我能够做到的最接近的方法是使用一个虚拟的typename U = T

  1. template <typename U = T, typename = std::enable_if_t<MyProperty<U>::value>>
  2. int f(int x) const {
  3. static_assert(std::is_same_v<T, U>, "不要提供 U.");
  4. return x;
  5. }

这个方法在 https://godbolt.org/z/8qGs6P8Wd 上可以工作,但感觉有点绕。是否有更好的方法?

  1. <details>
  2. <summary>英文:</summary>
  3. There are many similar-looking questions, but I can&#39;t find quite this one: Given
  4. ```cpp
  5. template &lt;typename T&gt;
  6. struct S {
  7. int f(int x) const { return x; }
  8. };

I want to be able to SFINAE-away f depending on some metafunction of T. The obvious thing doesn't work:

  1. #include &lt;type_traits&gt;
  2. template &lt;typename T&gt;
  3. struct MyProperty { static constexpr auto value = std::is_same_v&lt;T, float&gt;; };
  4. template &lt;typename T&gt;
  5. struct S {
  6. template &lt;typename = std::enable_if_t&lt;MyProperty&lt;T&gt;::value&gt;&gt;
  7. int f(int x) const { return x; }
  8. };
  9. int main() {
  10. S&lt;int&gt; s;
  11. }

The closest I can come is a dummy typename U = T:

  1. template &lt;typename U = T, typename = std::enable_if_t&lt;MyProperty&lt;U&gt;::value&gt;&gt;
  2. int f(int x) const {
  3. static_assert(std::is_same_v&lt;T, U&gt;, &quot;Don&#39;t provide U.&quot;);
  4. return x;
  5. }

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:

  1. template<class T>
  2. struct S {
  3. int f(int x) const requires MyProperty<T>::value {
  4. return x;
  5. }
  6. };

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 fs.

  1. template<class T>
  2. struct S {
  3. template<class U = T>
  4. std::enable_if_t<std::is_same_v<U, T> && MyProperty<U>::value, int>
  5. f(int x) const {
  6. return x;
  7. }
  8. };

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.

  1. struct empty {};
  2. template<class T>
  3. struct float_funcs {
  4. T& Self() { return *static_cast<T*>(this); }
  5. const T& Self() const { return *static_cast<const T*>(this); }
  6. // put all functions depending on the MyProperty trait being true here:
  7. int f(int x) const {
  8. return x + Self().foo; // `foo` is accessible since we're a friend
  9. }
  10. };
  11. template<class T> // added helper variable
  12. inline constexpr bool MyProperty_v = MyProperty<T>::value;
  13. // inherit from float_funcs<S<T>> if the condition is `true` or `empty` otherwise
  14. template<class T>
  15. struct S : std::conditional_t<MyProperty_v<T>, float_funcs<S<T>>, empty> {
  16. private:
  17. friend std::conditional_t<MyProperty_v<T>, float_funcs<S<T>>, empty>;
  18. int foo = 1;
  19. };

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:

  1. template&lt;class T&gt;
  2. struct S {
  3. int f(int x) const requires MyProperty&lt;T&gt;::value {
  4. return x;
  5. }
  6. };

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&lt;U, T&gt; in the enable_if too instead of in a static_assert, it'll be more SFINAE friendly in case you want to enable other fs.

  1. template&lt;class T&gt;
  2. struct S {
  3. template&lt;class U = T&gt;
  4. std::enable_if_t&lt;std::is_same_v&lt;U, T&gt; &amp;&amp; MyProperty&lt;U&gt;::value, int&gt;
  5. f(int x) const {
  6. return x;
  7. }
  8. };

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.

  1. struct empty {};
  2. template&lt;class T&gt;
  3. struct float_funcs {
  4. T&amp; Self() { return *static_cast&lt;T*&gt;(this); }
  5. const T&amp; Self() const { return *static_cast&lt;const T*&gt;(this); }
  6. // put all functions depending on the MyProperty trait being true here:
  7. int f(int x) const {
  8. return x + Self().foo; // `foo` is accessible since we&#39;re a friend
  9. }
  10. };
  11. template&lt;class T&gt; // added helper variable
  12. inline constexpr bool MyProperty_v = MyProperty&lt;T&gt;::value;
  13. // inherit from float_funcs&lt;S&lt;T&gt;&gt; if the condition is `true` or `empty` otherwise
  14. template&lt;class T&gt;
  15. struct S : std::conditional_t&lt;MyProperty_v&lt;T&gt;, float_funcs&lt;S&lt;T&gt;&gt;, empty&gt; {
  16. private:
  17. friend std::conditional_t&lt;MyProperty_v&lt;T&gt;, float_funcs&lt;S&lt;T&gt;&gt;, empty&gt;;
  18. int foo = 1;
  19. };

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&lt;T&gt; is not fulfilled. f doesn't even exist in any form in that case.

答案2

得分: 1

由于您正在使用C++20,您可以对函数进行约束:

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

否则,您可以使用继承:

  1. template <typename Self, typename T, typename = void>
  2. struct MyPropertyS {};
  3. template <typename Self, typename T>
  4. struct MyPropertyS<Self, T, std::void_t<std::enable_if_t<MyProperty<T>::value>>> {
  5. // 通过 `static_cast<Self&>(*this)` 访问类的其余部分
  6. int f(int x) const { return x; }
  7. };
  8. template<typename T>
  9. struct S : MyPropertyS<S<T>, T> {
  10. // 如果存在,则继承f
  11. };

(或者专门为整个结构体S本身提供特化,而不是继承有条件更改的部分)

这是您可以真正有条件地拥有非模板成员函数的唯一方式。

否则,您的模板方法很不错。您还可以考虑类似于以下方式:

  1. struct disabled {
  2. disabled() = delete;
  3. ~disabled() = delete;
  4. };
  5. template <typename T>
  6. struct S {
  7. int f(std::conditional_t<MyProperty<T>::value, int, disabled> x) const { return x; }
  8. };

这不会摆脱f,但您将无法调用它,s.f(0)将是SFINAE友好的失败(并且很容易为具有类型别名的多个函数重复使用)。

英文:

Since you are using C++20, you can constrain the function:

  1. template &lt;typename T&gt;
  2. struct S {
  3. int f(int x) const requires(MyProperty&lt;T&gt;::value) { return x; }
  4. };

Otherwise you can use inheritance:

  1. template &lt;typename Self, typename T, typename = void&gt;
  2. struct MyPropertyS {};
  3. template &lt;typename Self, typename T&gt;
  4. struct MyPropertyS&lt;Self, T, std::void_t&lt;std::enable_if_t&lt;MyProperty&lt;T&gt;::value&gt;&gt;&gt; {
  5. // Access rest of class via `static_cast&lt;Self&amp;&gt;(*this)`
  6. int f(int x) const { return x; }
  7. };
  8. template&lt;typename T&gt;
  9. struct S : MyPropertyS&lt;S&lt;T&gt;, T&gt; {
  10. // Inherit f if it exists
  11. };

(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:

  1. struct disabled {
  2. disabled() = delete;
  3. ~disabled() = delete;
  4. };
  5. template &lt;typename T&gt;
  6. struct S {
  7. int f(std::conditional_t&lt;MyProperty&lt;T&gt;::value, int, disabled&gt; x) const { return x; }
  8. };

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)

huangapple
  • 本文由 发表于 2023年6月8日 04:28:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/76426903.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定