【问题标题】:Constexpr cast to const char[]constexpr 强制转换为 const char[]
【发布时间】:2016-07-03 19:11:44
【问题描述】:

这发生在很多人身上,也发生在我身上。我一直在玩 C++ 中的编译时字符串。

我决定采用显然无法使用的方法:使用 template <char...> 类。

这是我想出来的,很常见,没什么特别的,而且也不管用。

template <char... chars> class string
{
public:

    static constexpr const char value[] = {chars...};

    constexpr string()
    {
    }

    constexpr operator decltype(value) & () const
    {
        return value;
    }
};

template <char... chars> constexpr const char string <chars...> :: value[];

我的想法是制作一个 string 实例 constexpr 可构造并公开某种 constexpr 转换,以便提供其内容。

现在,如果我这样做了

static constexpr const char x[] = "ciao";

template <const char * str> void print()
{
    std :: cout << str << std :: endl;
}

print <x> ();

这有效并且说ciao。如果我这样做,我也会得到ciao

std :: cout << string <'c', 'i', 'a', 'o'> {} << std :: endl;

print <string <'c', 'i', 'a', 'o', '\0'> :: value> ();

但是当我这样做时

print <string <'c', 'i', 'a', 'o', '\0'> {}> ();

我得到:No matching function for call to print

我肯定错过了一些东西。做我想做的事是不可行的吗?使实例执行 constexpr 强制转换以某种方式返回 value?如果可行,我将能够在编译时轻松地进行运算符和字符串操作,“唯一”的缺点是超无聊的'i', 'n', 'i', 't', 'i', 'a', 'l', 'i', 'z', 'a', 't', 'i', 'o', 'n'

进一步的实验

我做了另一个完美的实验。

template <char... chars> class string
{
public:
   constexpr string()
   {
   }

   constexpr operator size_t () const
   {
      return sizeof...(chars);
   }
};

template <size_t length> void print()
{
   std :: cout << length << std :: endl;
}

print <string <'c', 'i', 'a', 'o'> {}> ();

它会打印出漂亮的4

【问题讨论】:

  • 我认为那是因为您使 operator() 仅可调用左值(只是编造的),因此从临时调用它不能也不会编译。 :)
  • 我猜这与推断print 的类型名有关。我对何时无法推断类型没有充分的了解。 SO中的许多人都这样做。如果这是问题所在,希望他们中的一个能够解释它。
  • @MatteoMonti:没错,没关系!
  • 据我所知,您的代码在 C++17 中很好,但在 C++14 中不行。您的附加实验在 C++14 中很好,因为它使用了一个完整的非类型模板参数;此类参数的模板参数不像 C++14 和 11 中的指针那样受限制(它们允许转换的常量表达式)。 This answer 有一些相关信息。问题是,Clang 似乎是目前唯一实现了 C++17 更新规则的编译器(3.8.0 在 C++1z 模式下按预期编译您的代码)。
  • 有什么理由必须使用template &lt;const char * str&gt; void print() 而不是字符串<...>?

标签: c++ templates char constexpr


【解决方案1】:

在 C++14 中,对模板非类型形参的模板实参的限制是:

用于非类型、非模板的 模板参数 模板参数 应为以下之一:
* 对于整数或枚举类型的非类型模板参数模板参数类型的转换常量表达式(5.19);或
* 非类型模板参数的名称;或
* 一个常量表达式 (5.19),它指定具有静态存储持续时间和外部或内部链接的完整对象或具有外部或内部链接的函数的地址,包括函数模板和函数 template-ids,但不包括非静态类成员,表示(忽略括号)为&amp;id-expression,其中 id-expression 是对象或函数的名称,除了&amp; 如果名称引用函数或数组,则可以省略,如果相应的 template-parameter 是引用,则应省略;或
* 一个常量表达式,计算结果为空指针值(4.10);或
* 一个常量表达式,计算结果为空成员指针值(4.11);或
* 指向成员的指针,如 5.3.1 所述;或
* std::nullptr_t 类型的常量表达式。

在您的示例中,string&lt;'c', 'i', 'a', 'o', '\0'&gt;{} 不是这些。请注意,第一个项目符号仅限于整数或枚举类型,const char* 不是这两种类型(整数类型的例外是允许您的“进一步实验”示例编译)。其他项目符号均不适用。因此,符合 C++14 的应该拒绝您的代码。

解决方法是直接传递底层字符数组:

print<string<'c', 'i', 'a', 'o', '\0'>::value>();

或者将类型本身作为模板type参数,并在函数内打印::value

template <class T>
void print() {
    std::cout << T::value << std::endl;
}

print<string<'c', 'i', 'a', 'o', '\0'>>();

或者...


您会很高兴知道在 C++17 中,情况会变得更好。请参阅N4198,并附上激励示例:

template<int *p> struct A {};
int n;
A<&n> a; // ok

constexpr int *p() { return &n; }
A<p()> b; // error

该论文提出了对模板非类型参数的较少限制,这将使上述示例格式良好。新的措辞如下:

非类型模板参数模板参数应是模板参数类型的转换常量表达式(5.20)。对于引用类型或指针类型的非类型模板参数,常量表达式的值不应引用(或对于指针类型,不应是地址):
* 一个子对象 (1.8),
* 一个临时对象 (12.2),
* 字符串文字 (2.14.5),
* typeid 表达式 (5.2.8) 的结果,或 * 一个预定义的__func__ 变量(8.4.1)。

就是这样。其他一切都是允许的。

由于string&lt;'c', 'i', 'a', 'o', '\0'&gt;{} 转换为不是这些东西的指针,因此该示例的格式正确。 Clang 3.8 将在 c++1z 模式下(正确)编译您的示例,但不会在 c++14 模式下(正确)编译。 GCC 还没有实现这一点。只要给他们时间。

【讨论】:

    【解决方案2】:

    模板参数匹配不考虑用户定义的转换。

    但是,而不是

    print <string <'c', 'i', 'a', 'o', '\0'>{} > ();
    

    ……你可以写

    print <string <'c', 'i', 'a', 'o', '\0'>::value> ();
    

    或者你可以定义

    template <const char* str>
    void print()
    {
        std::cout << str << std::endl;
    }
    
    template< class Type >
    void print()
    {
        print<Type::value>();
    }
    

    ……然后就写吧

    print <x> ();
    print <string <'c', 'i', 'a', 'o', '\0'>> ();
    

    ...x 是您的命名空间范围

    static constexpr const char x[] = "ciao";
    

    注意:此代码使用 -std=c++14 的 MinGW g++ 5.1.0 编译,但不适用于 Visual C++ 2015 更新 2。此类差异对于突破语言边界的代码很常见。标准在不断变化,编译器也在不断发展以努力跟上标准,因此这样的代码在实践中不是很便携。

    【讨论】:

    • 实际上,我觉得有点难以相信模板参数匹配不考虑用户定义的转换...请参阅我添加的“进一步实验”部分。它工作得很好,我可以投很多东西,但显然不是const char *(或者我在这个过程中失去了constexprness)。
    • @MatteoMonti:哦,这很有趣。也许我错了。或者它可能是编译器特定的语言扩展。只有标准才能说明。抱歉,我现在没时间检查。
    猜你喜欢
    • 2018-01-01
    • 2023-03-15
    • 1970-01-01
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多