如何在C++17中定义用户自定义字符串字面量操作符模板?

4
根据我的C++17(16.5.8 [over.literal])(草案)版本以及cppreference.com,C++17应该支持用户定义字符串字面值的模板化操作符。
具体来说:
template <char...>
double operator "" _pi() {
    return 0.;
}

int main() {
  "test"_pi;
}

然而,gcc和clang都对我大喊大叫:

// gcc -Wall -Wextra -pedantic -std=c++17
error: no matching function for call to 'operator""_pi<char, 't', 'e', 's', 't'>()'
    7 |   "test"_pi;
      |   ^~~~~~~~~
note: candidate: 'template<char ...<anonymous> > double operator""_pi()'
    2 | double operator "" _pi() {

// clang -Wall -Wextra -pedantic -std=c++17
error: no matching literal operator for call to 'operator""_pi' with arguments of types 'const char *' and 'unsigned long', and no matching literal operator template

(在线演示)

他们似乎都想引导我使用以下模板,这个模板曾被短暂地认为是C++17的一部分,但据我了解并没有成为其中的一部分:

template <typename T, T...>
double operator "" _pi() {
    return 0.;
}

然而,gcc和clang都正确地发出了警告,指出这是一种非标准扩展,但它们编译得正确。gccclang都声称完全支持C++17核心语言。
我做错了什么?我的初稿和cppreference.com不准确吗?我目前无法访问最终的标准版本。

使用情况

我的使用情况是要实现以下内容,这就是为什么我需要编译时信息的原因。

template <char... cs>
constexpr auto operator "" _a() {
  return std::array<char, sizeof...(cs)>({cs...});
}
// "Hello"_a creates a std::array<char,5>

@cpplearner 很有趣。但这似乎是一个 C++17 之前的答案,在那个时候,它确实不是标准的一部分。 - bitmask
1个回答

4

char... 用户定义字面量不适用于字符串。它被称为数字字面量运算符模板,允许您拥有一个字面量运算符,将您使用的数字扩展为模板运算符的 Pack。例如,您可以像这样使用该运算符:

1234_pi

并且这将解析为调用

operator "" <'1', '2', '3', '4'> _pi()

现有的C++17标准无法满足您的需求。

为了实现这一点,您需要升级到C++20。在C++20中,您可以使用一个用户定义类型来构造一个字符串字面值,并使用它来构建 std::array 数组。这就是所谓的字符串字面值操作模板。

示例如下:

template<std::size_t N>
struct MakeArray
{
    std::array<char, N> data;
    
    template <std::size_t... Is>
    constexpr MakeArray(const char (&arr)[N], std::integer_sequence<std::size_t, Is...>) : data{arr[Is]...} {}
 
    constexpr MakeArray(char const(&arr)[N]) : MakeArray(arr, std::make_integer_sequence<std::size_t, N>())
    {}
};
 
template<MakeArray A>
constexpr auto operator"" _foo()
{
    return A.data;
}

然后

"test"_foo;

这将会被解析为一个 std::array<char, 5>。如果您不希望在数组中有空终止符,则只需要在使用char const(&arr)[N]部分以外的所有地方将N减去1

如果您不能使用C++20,可以将其转换为一些函数,例如

template <std::size_t N, std::size_t... Is>
constexpr auto as_array_helper(const char (&arr)[N], std::integer_sequence<std::size_t, Is...>)
{
    return std::array<char, N>{arr[Is]...};
}

template <std::size_t N>
constexpr auto as_array(const char (&arr)[N])
{
    return as_array_helper(arr, std::make_integer_sequence<std::size_t, N>());
}

然后你可以像这样使用它

as_array("test")    

1
但是,即使在C++20中,我们仍然无法拥有返回字符序列的UDL(用于传递编译时字符串):-/。 - Jarod42
这就解释了为什么我可以定义运算符,但却不能按预期使用它。很好的解释,纠正了我的错误认识 +1 - bitmask
有人知道UDL是否会成为C++的标准功能,例如在C++23中吗?GCC和Clang已经支持它,因此它肯定可以实现,我认为它非常有用,没有理由不让它成为“标准”! - fdev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接