Literal Initialization for Const References

Literal initialization for const references

So you can write code like this:

void f( const string & s ) {
}

f( "foobar" );

Although strictly speaking what is actually happening here is not the literal being bound to a const reference - instead a temprary string object is created:

string( "foobar" );

and this nameless string is bound to the reference.

Note that it is actually quite unusual to create non-parameter reference variables as you are doing - the main purpose of references is to serve as function parameters and return values.

C++20 : Memory allocation of literal initialization of const references

The easiest way to get what you want is just to make name_ a const char *:

class Test {
private:
const char *const name_;
int value_;
public:
Test(const char *name, int value) : name_(name), value_(value) {}
};

int main() {
Test test1("A", 1);
Test test2("B", 2);
Test test3("B", 3);
Test test4("A", 4);
Test test5("B", 5);
// etc ...
}

That works because the compiler will optimize strings in memory, so that in this case you don't need multiple copies of "A" in memory.

Unfortunately, the code you supply will not work, and leaves a dangling reference. The reason is that the rules for when to extend temporary object lifetimes extend when the reference is bound to a temporary materialization of an object. However, in your code, the temporary std::string is first bound to name, and then name is bound to name_. So the temporarily materialized std::string will be destroyed at the semicolon.

One downside to using a const char * is that you will lose length information in the event that you want to embed NUL characters ('\0') in your string. A potential work around would be to take the length of the string as a template argument, as in:

class Test {
private:
const char *const name_;
const std::size_t size_;
int value_;
public:
template<std::size_t N>
Test(const char name[N], int value)
: name_(name), size_(N-1), value_(value) {}
};

Class with string literal constructor won't work with const reference initialization

Thank you for your comments. Indeed it looks like a GCC bug.

Waiting for this being resolved, I finally found the following workaround in order to compile with GCC. I added a contructor overload which accept also only string literals but with a warning. Thus I can identify which line causes problem in my code.

#include <iostream>

struct test {
template <unsigned long int N>
test(const char(&)[N]){
std::cout << __PRETTY_FUNCTION__ << std::endl;
}

//Compatibility with GCC
[[gnu::deprecated("[WARNING] Don't use string literals on const reference !")]]
test(char *&&ptr){
std::cout << __PRETTY_FUNCTION__ << std::endl;
}
};

void func(const test&){}

int main(){
test val1 = "test";
const test val2 = "test";
const test &ref1 = "test";
const test &ref2 = (test)"test";
func("test");
}

Output with GCC :

g++ test.cpp -o test.exe && test.exe
test.cpp: In function 'int main()':
test.cpp:21:24: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
const test &ref1 = "test";
^~~~~~
test.cpp:21:24: warning: 'test::test(char*&&)' is deprecated: [WARNING] Don't use string literals on const reference ! [-Wdeprecated-declarations]
test.cpp:11:5: note: declared here
test(char *&&ptr){
^~~~
test::test(const char (&)[N]) [with long unsigned int N = 5]
test::test(const char (&)[N]) [with long unsigned int N = 5]
test::test(char*&&)
test::test(const char (&)[N]) [with long unsigned int N = 5]
test::test(const char (&)[N]) [with long unsigned int N = 5]

Output with clang :

clang++ test.cpp -o test.out && ./test.out
test::test(const char (&)[N]) [N = 5]
test::test(const char (&)[N]) [N = 5]
test::test(const char (&)[N]) [N = 5]
test::test(const char (&)[N]) [N = 5]
test::test(const char (&)[N]) [N = 5]

Passing literal as a const ref parameter

Does the compiler stick 42 somewhere (on the stack?) and pass its address to foo?

A temporary object of type const int is created, initialized with the prvalue expression 42, and bound to the reference.

In practice, if foo is not inlined, that requires allocating space on the stack, storing 42 into it, and passing the address.

Is there anything in the standard that dictates what is to be done in this situation (or is it strictly up to the compiler)?

[dcl.init.ref].

Besides ODR, why can't the compiler do whatever it did with 42 above?

Because according to the language, the reference is bound to the object bar::baz, and unless the compiler knows exactly what foo is doing at the point where it is compiling the call, then it has to assume that this is significant. For example, if foo contains an assert(&x == &bar::baz);, that must not fire with foo(bar::baz).

(In C++17, baz is implicitly inline as a constexpr static data member; no separate definition is required.)

Is there an elegant way to tell foo to accept x by value for primitive types?

There is generally not much point in doing this in the absence of profiling data showing that pass-by-reference is actually causing problems, but if you really need to do it for some reason, adding (possibly SFINAE-constrained) overloads would be the way to go.

Initializing reference member variable with literal

Well clang produces the following warning for this code even without any flags (see it live):

warning: binding reference member 'a' to a temporary value [-Wdangling-field]
ABC():a(43) { }
^~

gcc on the other hand requires either -Wall or -Wextra.

and if we check out this reference initialization reference it says:

a temporary bound to a reference member in a constructor initializer list persists only until the constructor exits, not as long as the object exists.

This can be found in the draft C++ standard section 12.2 Temporary objects paragraph 5 which includes the following bullet

— A temporary bound to a reference member in a constructor’s ctor-initializer (12.6.2) persists until the constructor exits.

Why can I assign an existing reference to a literal value in C++?

ival isn't a literal value, 1.01 is the literal value. It's been copied to ival which is a variable, which most definitely can have it's references assigned to another variable.

Constant function when its parameter is a reference to literal type

So, I think the return statement would be equivalent to the following

If by equivalent you mean that it would be copy-initialization had it been valid, then yes. But it's not valid, so to call it equivalent is not correct.

However, as the equivalent form, I wonder why the first code is well-formed when the object value copy-initialized from the operand rf?

Because copy-initialization is not of any importance here. The reason that

constexpr int value = func(0);

is valid, while

constexpr int value = rf;

is not, is solely due to the properties of the expression being used in the initialization. The copy-initialization plays no part in disqualifying the second or allowing the first.

For the first example, func(0) is the expression that must satisfy the constant expression requirements. It is an expression that produces a prvalue. That prvalue is ultimately copy-initialized from some reference. Yet the reference refers to some object that exists only during constant-evaluation. I.e. the "temporary" that is created to hold 0 before calling the function. That's why the prvalue result can be constant-evaluated, and then used to initialize a constexpr variable.

In the second example, rf on the other hand is not usable in a constant expression. For the very paragraph you cited. constexpr functions are still regular functions, invocable "at runtime" too. Also, the compiler doesn't have to constant fold them. And so they must be valid functions irrespective of when they are evaluated. As such, since rf is not usable in a constant expression irregardless of how the function is called, it cannot be used to initialize something that must be unconditionally initialized by a constant expression inside the function itself.

To put it a bit in the frame of standardese, the full-expression of initialization may access objects created within it. In the context of

constexpr int value = func(0);

rf is bound to an object that came into existence as part of the full-expression. So we may access it. However, in the context of

constexpr int value = rf;

rf is not bound to an object whole lifetime started in that full-expression.

Each constexpr variable declaration is its own self-contained "context" for checking the validity of the constant expression. They are disjoint from each other in that regard.



result is initialized with a constant expression but not a const object, So it violate this rule

It violates that bullet, but there are several bullets in that clause that are alternatives for valid lvalue-to-rvalue conversions. It is very much inline with

an lvalue-to-rvalue conversion unless it is applied to

  • a non-volatile glvalue of literal type that refers to a non-volatile object whose lifetime began within the evaluation of e;

result is of a literal type and came into life (as well as going out of it) during the evaluation of func(). It is therefore well-formed.



Related Topics



Leave a reply



Submit