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

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

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&#39;t find quite this one: Given
```cpp
template &lt;typename T&gt;
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 &lt;type_traits&gt;

template &lt;typename T&gt;
struct MyProperty { static constexpr auto value = std::is_same_v&lt;T, float&gt;; };

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

int main() {
    S&lt;int&gt; s;
}

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

    template &lt;typename U = T, typename = std::enable_if_t&lt;MyProperty&lt;U&gt;::value&gt;&gt;
    int f(int x) const { 
        static_assert(std::is_same_v&lt;T, U&gt;, &quot;Don&#39;t provide U.&quot;);
        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 fs.

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&lt;class T&gt;
struct S {
    int f(int x) const requires MyProperty&lt;T&gt;::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&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.

template&lt;class T&gt;
struct S {
    template&lt;class U = T&gt;
    std::enable_if_t&lt;std::is_same_v&lt;U, T&gt; &amp;&amp; MyProperty&lt;U&gt;::value, int&gt; 
    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&lt;class T&gt;
struct float_funcs {
    T&amp; Self() { return *static_cast&lt;T*&gt;(this); }
    const T&amp; Self() const { return *static_cast&lt;const T*&gt;(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&#39;re a friend
    }
};

template&lt;class T&gt; // added helper variable
inline constexpr bool MyProperty_v = MyProperty&lt;T&gt;::value;

// inherit from float_funcs&lt;S&lt;T&gt;&gt; if the condition is `true` or `empty` otherwise
template&lt;class T&gt;
struct S : std::conditional_t&lt;MyProperty_v&lt;T&gt;, float_funcs&lt;S&lt;T&gt;&gt;, empty&gt; {
private:
    friend std::conditional_t&lt;MyProperty_v&lt;T&gt;, float_funcs&lt;S&lt;T&gt;&gt;, empty&gt;;
    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&lt;T&gt; 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 &lt;typename T&gt;
struct S {
    int f(int x) const requires(MyProperty&lt;T&gt;::value) { return x; }
};

Otherwise you can use inheritance:

template &lt;typename Self, typename T, typename = void&gt;
struct MyPropertyS {};

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

template&lt;typename T&gt;
struct S : MyPropertyS&lt;S&lt;T&gt;, T&gt; {
    // 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 &lt;typename T&gt;
struct S {
    int f(std::conditional_t&lt;MyProperty&lt;T&gt;::value, int, disabled&gt; 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)

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:

确定