【问题标题】:When and how to use a template literal operator?何时以及如何使用模板文字运算符?
【发布时间】:2017-02-09 06:57:42
【问题描述】:

在cppreference 上提到可以有模板化 用户文字操作符,但有一些限制:

如果字面量操作符是一个模板,它必须有一个空的参数列表,并且只能有一个模板参数,它必须是元素类型为char的非类型模板参数包,比如

template <char...> double operator "" _x();

所以我在下面的代码中写了一个:

template <char...> 
double operator "" _x()
{
    return .42;
}

int main()
{
    10_x; // empty template list, how to specify non-empty template parameters?
}

问题:

  1. 代码可以工作,但是如何使用带有一些非空模板参数的运算符? 10_x&lt;'a'&gt;; 或 10_&lt;'a'&gt;x; 无法编译。
  2. 您有此类模板化运算符在实际使用中的任何示例吗?

【问题讨论】:

  • 我以前从未见过,但看起来想法是在编译时将前缀之前的字符视为chars,而不是将其视为数字文字并传递它到一个运行时函数。
  • @KyleStrand 但我仍然不确定你如何使用它(即编译时没有错误)。
  • 显然我也不是,因为我以前从未见过它!但我确实有一个我刚刚写的工作示例,所以我会发布它。
  • @KyleStrand Ohh 我的意思是编译成功(不是为什么要使用它)。

标签: c++ templates c++11 c++14 user-defined-literals


【解决方案1】:
10_x; // empty template list, how to specify non-empty template parameters?

这不太对。模板参数列表不为空。当你写:

template <char... Cs> 
??? operator "" _x()

Cs 由文字左侧的内容填充。也就是说,当你写:

10_x

调用:

operator ""_x<'1', '0'>();

一个简单的例子是构建一个编译时,溢出安全的二进制文字,这样:

template <uint64_t V>
constexpr uint64_t make_binary() {
    return V;
}

template <uint64_t V, char C, char... Cs>
constexpr uint64_t make_binary() {
    static_assert(C == '0' || C == '1', "invalid binary");

    return make_binary<2*V + C - '0', Cs...>();
}

template <char... Cs> 
uint64_t operator "" _b()
{
    static_assert(sizeof...(Cs) <= 64, "overflow");

    return make_binary<0, Cs...>();
}

uint64_t a = 101_b; // OK: a == 5
uint64_t b = 102_b; // error: invalid
uint64_t c = 11111111110000000000111111111100000000001111111111000000000011111111110000000000_b; // error: overflow

【讨论】:

  • 哦,我明白了,是的,非常有道理,因为运算符本身不接受任何参数......但似乎绝对没有办法访问那些 chars。
  • @vsoftco 你是什么意思?它们就像任何其他模板非类型参数一样。
  • 我的意思是他们没有名字,所以我不能直接在操作员里面把他们拉出来。但是@Kyle 的例子稍微澄清了一点。好的,现在我在 cmets 中看到了更多场景,我意识到你可以命名它们。
  • @vsoftco 你可以直接命名。
  • 这些的好处是,通过正确的constexpr 函数,它可以让您在编译时诊断溢出。
【解决方案2】:

您的模板参数已经指定——它们是构成您的文字值的源代码字符!所以对于10_x,你实际上是在调用:

template<> double operator "" _x<'1', '0'>();

这是一个工作示例。它编译没有错误,并且没有任何断言被触发。

#include <cassert>

enum class MyEnum
{
  ONE,
  TWO,
  THREE
};

template<char...> MyEnum operator "" _e();

template<> MyEnum operator "" _e<'1'>()
{
  return MyEnum::ONE;
}
template<> MyEnum operator "" _e<'2'>()
{
  return MyEnum::TWO;
}
template<> MyEnum operator "" _e<'3'>()
{
  return MyEnum::THREE;
}

int main()
{
  assert(1_e == MyEnum::ONE);
  assert(2_e == MyEnum::TWO);
  assert(3_e == MyEnum::THREE);
}

【讨论】:

  • 这是一个很好的例子!因此,访问chars 的唯一方法是通过专业化。
  • 不,您也可以这样做,例如template&lt;char... Cs&gt; const char* operator "" _cs() { static const char arr[] = {Cs..., '\0'}; return arr; }
  • @aschepler 我花了一段时间才意识到为什么可以重新初始化static:因为每个模板参数都有不同的运算符。
  • @vsoftco 是的,所以这不是真正的重新初始化——它是单独的 static 值的单独初始化。我敢肯定还有其他巧妙的技巧......
【解决方案3】:

您可以以某种方式详细说明参数包(正如其他人所提到的),或者如果您愿意,可以将它们作为编译时字符串访问:

template<int N>
constexpr double f(const char(&str)[N]) { return .42; }

template <char... C> 
constexpr double operator "" _x()
{
     return f({C...});
}

您有任何此类模板化运算符在实际使用中的示例吗?

您可以使用上面提到的技术来处理编译时 string-to-num 转换器,并使用10_x 之类的东西来代替f("10") 之类的东西。

【讨论】:

  • 你不能写warrior_x。
  • @Barry 够公平的。固定的。我曾经忘记文字运算符模板上的(让我说)constraints。谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-21
  • 1970-01-01
相关资源
最近更新 更多