You may be confused by "hello"
; it is a literal, but it (in a sense) is also an lvalue (if a const one). "hello"
creates an object that doesn't go away when the line ends, an array of const characters consisting of {'h', 'e', 'l', 'l', 'o', '\0'}
. Two different "hello"
could refer to the same object or not.
6
doesn't do the same thing; there is no persistant 6
in a C++ program with the constant 6
in it, there is a persistant "hello"
in a C++ program with the string constant "hello"
in it.
The literal 6
can cause a temporary to be instantiated. The lifetime of this temporary is until the end of the expression it is in (the "end of the line" as it where).
You cannot distinguish between the temporary created by 6
and the temporary returned from a function during a function call. This is fortunate, because both are temporaries with the same advantages and disadvantages.
A pointer to that temporary is going to be invalid at that point; even ==
or <
on that pointer is undefined behavior.
So a.cache(6)
is a bad plan.
Now, we could do something horrible.
unsigned long long const& operator""_lvalue(unsigned long long x) {
thread_local unsigned long long value;
value = x;
return value;
}
live example.
This creates a static storage duration lvalue value
and copies x
into it. So 6_lvalue + 4_lvalue
is going to be either 8 or 12, never 10, as the single lvalue is overwritten.
We can remove that overwriting problem with more template abuse.
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...>()>;
}
live example. About half of this is 0b
and 0x
and 0
binary/hex/octal support.
Use:
a.cache(6_lvalue);
Alternatively, in C++17 you can do this:
template<auto x>
constexpr auto lvalue = x;
or in C++14
template<class T, T x>
constexpr T lvalue = x;
with
#define LVALUE(...) lvalue<std::decay_t<decltype(__VA_ARGS__)>, __VA_ARGS__>
In C++17 it is lvalue<7>
and in C++14 it is LVALUE(7)
.
template <typename T, T N> constexpr T cliteral = N;
, usage:a.cache(cliteral<int, 6>)
. In C++17:template <auto N> inline constexpr auto cliteral = N;
, usage:a.cache(cliteral<6>)
. – ReprehendMyClass
objects are constructed with a macro as locals and live only until they leave their scope so caching other locals constructed before them is fine. – ZigzagL
won't do anything? – ZigzagL
suffix creating an lvalue is the most hilarious thing I've heard all week :-) – Reprehend#define LITERAL_TO_LVALUE(x) cliteral<decltype(x), x>
. You can put your comment as an answer and I will accept it – Zigzaga.cache(6);
? – Derwood6
is valid after the end of the statement the6
is in. So caching a pointer to6
ina.cache(6)
is not okay. – Revel6_L
be an lvalue. I might make it more explicit:6_lvalue
. – Revel6
". No such pointer can eve exist, because6
is a prvalue. You would always first initialize an object from this prvalue, and then you can take the address of that object. The literal itself is not an object. C++17 makes this a bit more obvious with its improved value categories, but that's basically how it has always worked. – Reprehend