尝试区分不同类型的rvalue——字面量和非字面量。

6
我希望有一个只能接受左值的方法,所以我做了以下操作:
template <typename T>
MyClass& cache(T&) {}

template <typename T>
MyClass& cache(const T&&) = delete;

这很好用——我甚至可以传递C字符串字面量,因为它们也是左值。

我需要左值的原因是因为我正在缓存指向传递对象的指针,这意味着它们不能是临时对象。

以下代码有效:

MyClass a;
a.cache("string literal");

int temp = 6;
a.cache(temp);

以下代码(按照预期)无法正常工作:
int getInt(); // fwd decl
a.cache(getInt());

但我也希望能够传递其他字面量 - 但它们似乎都是右值...

以下代码无法正常工作(但我希望它能):

MyClass a;
a.cache(6);

有没有办法区分这些字面量和非字面量rvalue?
有没有一种简单的方法将这些字面量转换为lvalue?我在Stack Overflow上找到了这个答案,提到了类似于unless you write 1L的东西,但是即使我给一个字面量添加L后缀,它仍然是一个临时值。
即使使用宏也可以 - 类似于这样:a.cache(TURN_TO_LVALUE(6));(或CONSTANTIZE(6)

在C++14中:template <typename T, T N> constexpr T cliteral = N;,用法:a.cache(cliteral<int, 6>)。在C++17中:template <auto N> inline constexpr auto cliteral = N;,用法:a.cache(cliteral<6>) - Kerrek SB
@KerrekSB,所以我也会使用这样的宏:#define LITERAL_TO_LVALUE(x) cliteral<decltype(x), x>。您可以将您的评论作为答案,我会接受它。 - onqtam
你为什么认为在 a.cache(6); 中缓存指向参数的指针是可以接受的? - aschepler
@aschepler,你为什么认为这不好呢?我正在为我的测试框架(doctest)实现日志功能-请看这里。我缓存对象的指针,只有在同一作用域中出现错误时才使用这些指针将它们所指向的对象转换为字符串并惰性地构造消息。如果您对这种设计有任何问题,我很乐意接受反馈 :) 我确实缺少同行评审... - onqtam
@KerrekSB 是的,我在下面的回答中更加小心措辞。 :) 完全区分某些情况下实例化的临时变量和字面上的6本身将占用评论中允许使用的一半字符数。 @_@ - Yakk - Adam Nevraumont
显示剩余7条评论
1个回答

5
你可能会对"hello"感到困惑;它是一个字面量,但在某种意义上它也是一个(如果是const的)"hello"创建了一个对象,该对象不会在行结束时消失,它是由{'h','e','l','l','o','\0'}组成的const字符数组。两个不同的"hello"可能指向相同的对象,也可能不是。

6并不会做同样的事情;在C++程序中,具有常量6的程序中没有持久的6,而是有一个带有字符串常量"hello"的C++程序中有一个持久的"hello"

字面量6可能会导致临时实例化。这个临时实例的生命周期直到它所在的表达式结束(就像"行末"一样)。

您无法区分由6创建的临时值和函数调用期间返回的临时值。这是幸运的,因为两者都具有相同的优缺点。
此时指向该临时值的指针将失效; 即使在该指针上执行 == < 也是未定义的行为。
因此,a.cache(6)是一个不好的选择。
现在,我们可以做一些可怕的事情。
unsigned long long const& operator""_lvalue(unsigned long long x) {
    thread_local unsigned long long value;
    value = x;
    return value;
}

实时示例

这将创建一个静态存储持续时间的lvalue value并将x复制到其中。因此,6_lvalue + 4_lvalue将是8或12,永远不会是10,因为单个lvalue被覆盖了。

我们可以通过更多的模板滥用来解决这个覆盖问题。

template<int P>
constexpr unsigned long long pow_( unsigned x, std::size_t tens ) {
  if (tens == 0) return x;
  return P*pow_<P>(x, tens-1);
}
template<int base>
constexpr unsigned long long ucalc(std::integer_sequence<char>) {
  return 0;
}
constexpr unsigned digit( char c ) {
    if (c >= '0' && c <= '9') return c-'0';
    if (c >= 'a' && c <= 'z') return c-'a'+10;
    if (c >= 'A' && c <= 'Z') return c-'A'+10;
    exit(-1);
}
template<int base, char c0, char...chars>
constexpr unsigned long long ucalc(std::integer_sequence<char, c0, chars...>) {
  return pow_<base>( digit(c0), sizeof...(chars) ) + ucalc<base>( std::integer_sequence<char, chars...>{} );
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, chars...>) {
  return ucalc<10>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', 'x', chars...>) {
  return ucalc<16>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', 'X', chars...>) {
  return ucalc<16>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', 'b', chars...>) {
  return ucalc<2>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', 'B', chars...>) {
  return ucalc<2>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc(std::integer_sequence<char, '0', chars...>) {
  return ucalc<8>(std::integer_sequence<char, chars...>{});
}
template <char... chars>
constexpr unsigned long long calc() {
  return calc( std::integer_sequence<char, chars...>{} );
}
template<class T, T x>
constexpr T lvalue = x;
template <char... chars>
unsigned long long const& operator "" _lvalue() {
  return lvalue<unsigned long long, calc<chars...>()>;
}

实例演示。其中大约有一半是 0b0x0 二进制/十六进制/八进制支持。

用法:

a.cache(6_lvalue);

另外,在C++17中,你可以这样做:

template<auto x>
constexpr auto lvalue = x;

或者在 C++14 中
template<class T, T x>
constexpr T lvalue = x;

使用

#define LVALUE(...) lvalue<std::decay_t<decltype(__VA_ARGS__)>, __VA_ARGS__>

在C++17中,它是lvalue<7>,而在C++14中则是LVALUE(7)

1
变量模板即使在C++14中也不会遇到ODR问题(可能需要使用extern以确保它具有外部链接 - 这是一个单独的核心问题)。 - T.C.
@T.C. 我并不经常使用模板变量;你有没有在某个独特的地方实例化它们的要求?我曾经以为这就是我们添加“inline”变量的原因(显然是错误的)。或者它们是为非模板变量(例如模板类的非模板变量)而添加的? - Yakk - Adam Nevraumont
1
“inline” 主要用于非模板变量和静态数据成员。内联变量与变量模板之间的关系有点类似于内联函数和函数模板之间的关系。 - T.C.

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