g++: "error: unable to find string literal operator"
g++: "warning: invalid suffix on literal"
我:之前写的代码还能好好地编译,使用了带有C++11编译选项后编译器居然说代码有问题,肯定是编译器错了!!!
#include <cstdio>
#define _SLO_ "string literal operator."
int main()
{
printf("Hello "_SLO_);
return 0;
}
上述代码无法通过编译,然而,如果在_SLO_前面加一个空格,又可以正确地编译了,这是为什么呢?
问题解答
原来,C++11标准中增加了一种称为 “literal operator” 的操作符,用户可以采用这种方法来自定义新的操作,以便于提供易于读懂的快捷语法。例如,我们需要实现一种度量距离的类:
class Dist {
long double meter;
public:
Dist(long double m)
: meter(m) {}
};
在国际单位中,使用米(meter)来度量距离,但有时候使用其它的度量更能使人理解,例如:两地的距离958km,屏幕的对角线距离9.7inch, 碳原子半径91pm等等。 如果在定义距离的时候每次都加上一个数量级,会给阅读代码带来困难且不易于维护,采用literal operator,可以定义以下函数解决这个问题。
Dist operator "" _km(long double km)
{
return Dist(km * 1000);
}
// 定义了函数之后,就可以通过以下方式来构造一个距离对象:
Dist dist = 1.23_km;
Literal operator
常见的Literal operator 参数列表如下:
operator "" identifier (const char *);
operator "" identifier (unsigned long long int);
operator "" identifier (long double);
operator "" identifier (char);
operator "" identifier (const char*, std::size_t);
// 后两种还有对应的 wchar_t, char16_t, char32_t 版本,此处略去
其中, identifier 是一个以下划线 “_” 开始的字符串,不以下划线开始的Literal operator 被保留给标准库使用。用户只能自定义以上类型的Literal operator, 比如用户不能定义unsigned int、double等类型的Literal operator, 甚至不能定义 long long int 类型的Literal operator。
Dist dist = -1_km; // error: no match for 'operator-'
Dist dist = -(1_km); // error: no match for 'operator-'
第一种方法在编译器看来和第二种相同,负号被解析为一个一元操作符,因此首先调用operator””_km,然后再执行operator-,由于Dist类没有重载负号,因此编译器会报错。
Literal operator template
如果字面值操作符是一个模板,它必须具有空的参数列表,且只能有一个char… 类型的模板参数,即
template<char...> result_type operator "" identifier();
例如我们需要实现一个三进制转换函数,则下面的字面值操作符模板可以完成这项工作:
#include<iostream>
using namespace std;
template<unsigned long long R>
constexpr unsigned long long to_ternary()
{
return R;
}
template<unsigned long long R, char ch, char... C>
constexpr unsigned long long to_ternary()
{
static_assert(ch == '0' || ch == '1' || ch == '2', "invalid ternary.");
static_assert(3*R+(ch-'0') > R, "invalid ternary.");
return to_ternary<3*R+(ch-'0'), C...>();
}
template<char... C>
unsigned long long operator "" _ternary()
{
return to_ternary<0ULL, C...>();
}
int main()
{
cout << 1002_ternary << endl; // 29
return 0;
}
有的读者可能会问:这么简单的转换只需要一个循环就足够了,为什么要写成好几个函数模板,让问题变得这么复杂?实际上,这种方法只能应用于字面值常量,对于变量字符串则无能为力,而对字面值常量采用这种方法有以下几种优点:可以在编译期判断是否有非法字符;可以在编译期判断是否溢出;在编译器优化的情况下,以上函数模板没有运行时开销;采用字面值操作符(在某些情况下)更容易读懂。 C++的语法用于向编写者提供各种不同的工具,而是否使用、如何使用、何时使用完全取决于代码的编写者,解决某个问题往往会有很多种方法。
名称查找
当编译器遇到一个用户定义的字面值以后,将会采用以下策略查找函数定义:
1. 若字面值为一个整数类型
如果存在一个参数类型为unsigned long long 的字面值操作符函数,则调用这个函数;
否则,函数重载中必须有且仅有一个字符串类型参数的函数或模板函数,并调用这个函数。
2. 若字面值为一个浮点数类型
如果存在一个参数类型为long double 的字面值操作符函数,则调用这个函数;
否则,函数重载中必须有且仅有一个字符串类型参数的函数或模板函数,并调用这个函数。
3. 若字面值为字符串,则调用 operator “” identifier (str, len),其中str为字符串常量,len为其长度
4. 若字面值为字符型,则调用 operator “” identifier (ch),其中ch为字符。
long double operator "" _x(long double d){return d;};
std::u16string operator "" _x(const char16_t* str, size_t len){return u16string(str);};
unsigned long long operator "" _x(const char* str){ return stoull(str); }
int main()
{
auto a = 3.14_x; // operator "" _x(3.14L)
auto b = u"string"_x; // operator "" _x(u"string", 6)
auto c = 12345_x; // operator "" _x("12345")
auto d = "12345"_x; // error, not operator "" _x("12345")
}
标准库中的应用(C++14)
#include<iostream>
#include<string>
#include<chrono>
#include<complex>
using namespace std;
using namespace std::literals;
int main()
{
auto three_hour = 3h; // chrono::duration: 3 hour
auto half_second = 0.5s; // chrono::duration: 0.5 second
auto ten_minutes = 10min; // chrono::duration: 10 minutes
// ...
complex<double> cp = 1.2 + 3.4i; // complex<double>: 1.2 + 3.4i
complex<long double> cpl = 1.2L + 3.4il; // complex<long double>: 1.2 + 3.4il
complex<float> cpf = 1.2f + 3.4if; // complex<float>: 1.2f + 3.4if
string s1 = "string\0\0literal"; // s1 == "string"
string s2 = "string\0\0literal"s; // s2 == "string\0\0literal"
cout << s1 << '\n' << s2 << endl;
return 0;
}
注意到if是C++关键字,C++标准允许将关键字作为字面值操作符的标识符,但””和标识符之间不能包含空格:
long double operator ""int (long double); // ok
long double operator "" int (long double); // error
总结
Literal operator 在一定程度上使得代码中的常量具有更加明确的意义,如果好好使用字面值操作符会让代码更容易读懂,但如果滥用造成词不达意,也会给阅读带来麻烦。 总之,C++标准提供了这项功能,能不能用好,就看你怎么写了。
本文由kedixa发表于个人博客,
转载请注明作者及出处。
本文链接:https://blog.kedixa.top/2017/cpp-literal-operator/