I want to have a method that can be called only with lvalues so I've done the following:
template <typename T>
MyClass& cache(T&) {}
template <typename T>
MyClass& cache(const T&&) = delete;
And this works fine - I can even pass C string literals since they are also lvalues.
The reason I need lvalues is because I'm caching pointers to the passed objects - and that means they cannot be temporaries.
The following code works:
MyClass a;
a.cache("string literal");
int temp = 6;
a.cache(temp);
And the following code (as desired) does not work:
int getInt(); // fwd decl
a.cache(getInt());
But I also want to be able to pass other literals - but they all seem to be rvalues...
The following code does not work (but I wish it could):
MyClass a;
a.cache(6);
Is there a way to distinguish between such literals and non-literal rvalues?
Is there a way to easily turn such literals into lvalues? I found this answer on Stack Overflow mentioning something like unless you write 1L
but even if I give an L
suffix to a literal it is still a temporary.
Even a macro is OK - something like this: a.cache(TURN_TO_LVALUE(6));
(or CONSTANTIZE(6)
)
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)
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With