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

1

6

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

Zigzag answered 1/5, 2017 at 12:12 Comment(12)
what if I pass a referenence to a local variable? You should rather document you function properly.Accustomed
In C++14: 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>).Reprehend
@TheTechel it's fine to use locals - MyClass 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.Zigzag
@KerrekSB so using a suffix (like suggested in the other SO question) like L won't do anything?Zigzag
@onqtam: I still can't tell whether it's April 1 or whether this is the most elaborate prank ever, but the idea of the L suffix creating an lvalue is the most hilarious thing I've heard all week :-)Reprehend
@KerrekSB So I'll also use a macro like this: #define LITERAL_TO_LVALUE(x) cliteral<decltype(x), x>. You can put your comment as an answer and I will accept itZigzag
Why do you think it's okay to cache a pointer to the argument in a.cache(6);?Derwood
@Derwood And why don't you think it's okay? I'm implementing logging functionality for my testing framework (doctest) - see here. I cache pointers to objects and only if something in the same scope fails do I use those pointers to stringify the objects they point to and construct the message lazily. If you see a problem with that design I'm happy to receive feedback :) I'm lacking peer-review anyway...Zigzag
@Zigzag No pointer to 6 is valid after the end of the statement the 6 is in. So caching a pointer to 6 in a.cache(6) is not okay.Revel
@KerrekSB I believe can make 6_L be an lvalue. I might make it more explicit: 6_lvalue.Revel
@Yakk: It's not even legit to talk about "a pointer to 6". No such pointer can eve exist, because 6 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
@KerrekSB Yes, I was far more careful in wording in my answer below. :) Fully distinguishing between the temporary instantiated in certain circumstances from the literal 6 from the literal 6 itself would eat up like half the characters allowed in a comment. @_@Revel
R
5

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

Revel answered 1/5, 2017 at 13:25 Comment(3)
Variable templates don't suffer from ODR problems even in C++14 (possibly with an extern to be absolutely sure that it has external linkage - that's a separate core issue).Shufu
@Shufu I don't use template variables that much; there isn't a requirement that you instantiate them somewhere uniquely? I was under the (apparently mistaken) impression that that was why we added inline variables. Or where they added for non-template variables (such as non-template variables of a template class)?Revel
inline is primarily for non-template variables and static data members. The relationship between inline variables and variable templates is kind of similar to the relationship between inline functions and function templates.Shufu

© 2022 - 2024 — McMap. All rights reserved.