Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

trying to distinguish between different kinds of rvalues - literals and non-literals

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))

like image 220
onqtam Avatar asked May 01 '17 12:05

onqtam


1 Answers

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).

like image 55
Yakk - Adam Nevraumont Avatar answered Sep 17 '22 19:09

Yakk - Adam Nevraumont