如何检查是不是可以从给定类型构造模板类型

Posted

技术标签:

【中文标题】如何检查是不是可以从给定类型构造模板类型【英文标题】:How to Check if a Template Type can be Constructed from a Given Type如何检查是否可以从给定类型构造模板类型 【发布时间】:2020-01-24 00:02:21 【问题描述】:

如果模板参数是std::string 的任何类型,我正在尝试使用std::enable_if_t 来启用一个类(显示如何使用来自多个库的通用字符串的答案的奖励积分)。

我不想做“如果Textconst char*std::stringstd::string_view 等”。本质上,我不想具体提及每一个可能的类似字符串的对象。我对这个对象要做的就是将它打印到控制台,Text 对象将被存储为类属性。

有没有比考虑每种类型更优雅的方法?

【问题讨论】:

如果成员是printable(无论这意味着什么),您是否希望能够在您的班级中拥有一个函数print?这也包括ints 和其他原始类型,但为了打印到控制台,ints 在我看来非常像字符串 @StefanGroth 如果您查看 ALX23z 对另一个答案的第一条评论,我准确地定义了我所说的可打印 :) 【参考方案1】:

您可以为此使用is_detected。我们正在尝试检查给定的类型是否可以使用operator<<std::couts 重载之一进行打印,或者该类型本身是否提供了operator<< 用于打印到std::cout。有关我如何实现此功能的更一般说明,请查看 https://www.fluentcpp.com/2017/06/02/write-template-metaprogramming-expressively/

首先,我们为 std::cout 本身的重载定义一个合适的is_detected

// check std::cout.operator<<(T )
template<typename = void, typename Arg = void> struct test_operator_of_cout : std::false_type ;

template<typename Arg>
struct test_operator_of_cout<std::void_t<decltype(std::cout.operator<<(std::declval<Arg>()))>, Arg>
    : std::true_type ;

template<typename Arg>
constexpr bool test_operator_of_cout_v = test_operator_of_cout<void, Arg>::value;

还有一个用于operator&lt;&lt;(ostream&amp;, T ) 的所有重载。我在上面发布的链接对此进行了概括,以减少代码冗余。

// check operator<<(std::cout, T )
template<typename = void, typename Arg = void> struct test_operator_of_struct : std::false_type ;

template<typename Arg>
struct test_operator_of_struct<std::void_t<decltype(operator<<(std::cout, std::declval<Arg>()))>, Arg>
    : std::true_type ;

template<typename Arg>
constexpr bool test_operator_of_struct_v = test_operator_of_struct<void, Arg>::value;

我们现在可以使用这些类型特征通过 enable_if 来实现打印功能:

template<typename T> struct MyClass 

    T t;

    template<
        typename Consider = T,
        typename = std::enable_if_t<
            ( test_operator_of_cout_v<Consider> || test_operator_of_struct_v<Consider>) 
            && !std::is_arithmetic_v<Consider>
        >
    > void print() 
        std::cout << t;
    

;

这里有两点需要注意:

您需要Consider = T 的第一个模板参数。否则,编译器将尝试实例化函数的声明,对于不满足条件的类型,该声明是错误的。查看此 SO-Answer 以获得更深入的解释:std::enable_if to conditionally compile a member function 由于!std::is_arithmetic,算术类型不可打印。我个人不会包含此内容,因为我看不出为什么我的课程不应该允许完全可打印的类型可打印。

现在我们可以看看什么是可打印的,什么是不可打印的:

struct NotPrintable ;

struct Printable 
    friend std::ostream& operator<<(std::ostream& os, const Printable& p) 
         return os;
    
;

auto foo() 

    MyClass<const char *> chars;
    chars.print(); //compiles

    MyClass<std::string> strings;
    strings.print(); //compiles

    MyClass<std::string_view> string_views;
    string_views.print(); //compiles

    MyClass<Printable> printables;
    printables.print(); // compiles

    // MyClass<int> ints;
    // ints.print(); // Does not compile due to !is_arithmetiv_v

    // MyClass<NotPrintable> not_printable;
    // not_printable.print(); //Does not compile due to operator checking


您可以在此处查看完整示例:https://godbolt.org/z/ZC9__e

【讨论】:

【参考方案2】:

查看 type_traits 库:

https://en.cppreference.com/w/cpp/header/type_traits

您可能对is_constructibleis_convertibleis_assignable 感兴趣,具体取决于您要使用的具体方法。

例如is_constructible&lt;T, Args...&gt; 测试是否有构造函数T::T(Args...)

is_convertible&lt;From, To&gt; 测试To 是否可以转换为From:例如,以下代码编译From Y = (From)X;,其中XTo

【讨论】:

我确实调查了这个,有趣的是,在我发布问题后发现了这些。尽管如此,我认为std::string_view 是这些问题的根源。有没有办法处理一般的“可打印”类型(即不是数字但可以打印到std::cout的类型)? @DrakeJohnson 我相当肯定std::string_view 可以转换为std::string,不是吗? 它显示is_convertiblestring_viewstring 为假。但是,看看 STL 在 &lt;string&gt; 标头中是如何做到的,他们定义了一个模板 typedef(_StringViewIsh 类型)using _Is_string_view_ish = enable_if_t&lt;conjunction_v&lt;is_convertiable&lt;const _StringViewIsh&amp;, basic_string_view&lt;_Elem, _Traits&gt;&gt;, negation&lt;is_convertable&lt;const _StringViewIsh&amp;, const _Elem*&gt;&gt;&gt;&gt;; 问题是这有点难以理解。 @DrakeJohnson 你可以做另一件事 - std::string 可转换为 std::string_view。因此,您可以轻松编写将任何内容转换为 std::string_view 并打印它的函数 - 或者只是尝试打印它而不尝试转换(至少对 const char* 进行专门化)。

以上是关于如何检查是不是可以从给定类型构造模板类型的主要内容,如果未能解决你的问题,请参考以下文章

如何在模板类定义中构造自定义类型

std::list 模板在其实例类型中是不是需要复制构造函数(或等效项)?

如何验证某些模板 *not* 为给定的参数类型编译?

如何允许我的模板化 Vector 允许没有默认(空)构造函数的类型?

C++--模板的概念和意义深入理解函数模板类模板的概念和意义

非模板类的模板构造函数出现问题[重复]