如何仅在提供模板参数时启用类模板成员函数?

How can you enable a class template member function only if a template argument was provided?

提问人:yonutix 提问时间:9/26/2023 最后编辑:Jan Schultkeyonutix 更新时间:9/26/2023 访问量:183

问:

是否可以有一个带有可选模板参数的类,可以像这样调用?

#include <iostream>

template <typename T = void>
class A final
{
public:
    // This class can be called only when T exists.
    void f()
    {
        printf("%d\n", member);
    }

    // This method can be called only when T is missing.
    void g()
    {
        printf("No template parameter\n");
    }
public:
    T member;
};

int main()
{

    A<int> a1;
    A a2;
    a1.f(); // should be valid
    a1.g(); // should be invalid, cannot compile
    a2.f(); // should be invalid, cannot compile
    a2.g(); // should be valid

    return 0;
}

如果是,应该使用哪些标准函数?

C 模板 ++20 C ++-concepts 成员函数

评论

3赞 Some programmer dude 9/26/2023
如果用户正在做,是否应该认为是“失踪”?A<void>T
2赞 NathanOliver 9/26/2023
让这些功能工作很容易。拥有可选的类成员变量更难。如果您只这样做一次,那么添加专业化将是一种简单的方法来处理此问题。如果你想在许多课程中做到这一点,那么我建议一个不同的策略。void
0赞 aschepler 9/26/2023
@Fareanor 在 C++20 及更高版本中,模板约束是 SFINAE“技巧”的更清晰、更简单的替代方案,因此 、 等几乎从来都没有用。std::enable_ifstd::void_t
0赞 Fareanor 9/26/2023
@aschepler我删除了我的评论,因为它对成员函数(即在模板类中)没有用。std::enable_if
1赞 Jarod42 9/26/2023
可变参数模板可能更适合处理“未提供”。

答:

7赞 dfrib 9/26/2023 #1

成员函数 API 的约束

您可以利用约束和概念,尤其是 std::same_as 概念:

#include <concepts>

template <typename T = void>
struct A final {
    // This method is defined only when T is non-void.
    void f() requires (!std::same_as<T, void>) {} 

    // This method is defined only when T is void.
    void g() requires std::same_as<T, void> {} 
};

int main() {
    A<int> a1;
    A a2;
    a1.f(); // OK
    // a1.g(); // error
    // a2.f(); // error
    a2.g(); // OK
}

会员呢?

如果您只希望该成员存在于某些专业化中,则可以将其包装在相应地专门化的类模板中。例如:

template<typename T>
struct Value {
    T value;
};

template<>
struct Value<void> {};

另一种方法是使用帮助程序基类来继承成员,这在 A 的构造函数依赖于类型本身的特征时尤其有用。F.D.(英语:F.D.) 通常使用各种基类的 mixin 实现。std::optional

您可能还希望对类的模板参数本身进行限制,例如在类中包装哪种类型是有意义的。A

使用最小方法:

#include <concepts>

template<typename T>
struct Value {
    T value;
};

template<>
struct Value<void> {};

template <typename T = void>
struct A final {
    // This methods can be called only when T is non-void.
    T f() requires (!std::same_as<T, void>) {
        return member.value;
    } 

    // This method can be called only when T is void.
    void g() requires std::same_as<T, void> {}
private:
    Value<T> member;
};

int main() {
    A<int> a1;
    A a2;
    auto value = a1.f(); // OK
    // a1.g(); // error
    // a2.f(); // error
    a2.g(); // OK
}

评论

0赞 aschepler 9/26/2023
虽然这太糟糕了,但据我所知,只有当是时才没有办法给出属性。我想如果需要的话,私人继承黑客可能会有所帮助。member[[no_unique_address]]Tvoid
0赞 Daniel Langr 9/26/2023
@aschepler 它会有什么帮助吗?无论如何,一个对象都会占用(至少)1 个字节,即使 的存储被优化了。A<void>member
0赞 Caleth 9/26/2023
如果它是其他类的成员,则@DanielLangr不行[[no_unique_address]]
0赞 Jarod42 9/26/2023
@DanielLangr:如果包含其他成员,会有所帮助。A
0赞 Daniel Langr 9/26/2023
@Jarod42我同意。但是,EBO和在这方面有什么区别吗,正如aschepler所假设的那样?AFAIK,此属性甚至可以用于非空类型的成员变量。[[no_unique_address]]
8赞 Jarod42 9/26/2023 #2

您可以将“旧”方式用于专业化:

template <typename T = void>
class A final // generic case, T != void
{
public:
    void f() { std::cout << member << std::endl; }

public:
    T member;
};

template <>
class A<void> final
{
public:
    void g() { printf("No template parameter\n"); } // actually `void` and not "No".
};

通过将类转换为可变参数,将 absent 参数作为空包而不是 void 进行处理,您可以执行以下操作:

template <typename... Ts>
class A final
{
static_assert(sizeof...(Ts) < 2);
public:
    void f() requires (sizeof...(Ts) == 1) { std::cout << std::get<0>(member) << std::endl; }
    void g() requires (sizeof...(Ts) == 0) { printf("No template parameter\n"); }

public:
    std::tuple<Ts...> member;
};

评论

3赞 NathanOliver 9/26/2023
我喜欢元组方法,它对我来说非常开箱即用。它还提供了相同的尺寸,因此没有额外的膨胀。