lundi 4 juin 2018

Parameter pack and constexpr static

Using parameter pack expansion with rvalue references and const class member data causes a link error on Clang 5 and 6, and GCC 4.8.5 and 5.3. I first encountered the problem with a function with the same signature as std::make_shared but have simplified it to the following test case showing 5 different ways that I would expect to work.

#include <utility>

constexpr int global_value = 3;

struct Foo {
    static constexpr int value = 3;
};

struct Bar {
    static const int value = 3;
};

template<class... Args>
int* make(Args&&... args)
{
    return new int(std::forward<Args>(args)...);
}

template<class... Args>
int* make_nofwd(Args&&... args)
{
    return new int(args...);
}

template<class... Args>
int* make_norvalue(Args... args)
{
    return new int(args...);
}

int main(int, char** )
{
#if ATTEMPT == 0
    int* v = make(Foo::value); // FAIL
#elif ATTEMPT == 1
    int* v = make_nofwd(Foo::value); // FAIL
#elif ATTEMPT == 2
    int* v = make_norvalue(Foo::value); // OK
#elif ATTEMPT == 3
    int* v = make(global_value); // OK
#elif ATTEMPT == 4
    int* v = make(Bar::value); // OK
#endif
    return (*v == 3 ? 0 : 1);
}

Running each attempt shows that the struct's constant and constexpr data aren't getting inserted when passed as rvalues.

$ for i in {0..4}; do echo "=== Attempt $i ==="; g++ derp.cc -Wall -Wextra -std=c++11 -D ATTEMPT=$i -o derp.exe && ./derp.exe && echo "SUCCESS" ; done
=== Attempt 0 ===
/tmp/ccC5TyiZ.o: In function `main':
derp.cc:(.text+0x10): undefined reference to `Foo::value'
collect2: error: ld returned 1 exit status
=== Attempt 1 ===
/tmp/ccCwlV85.o: In function `main':
derp.cc:(.text+0x10): undefined reference to `Foo::value'
collect2: error: ld returned 1 exit status
=== Attempt 2 ===
SUCCESS
=== Attempt 3 ===
SUCCESS
=== Attempt 4 ===
/tmp/ccnfzmHk.o: In function `main':
derp.cc:(.text+0x10): undefined reference to `Bar::value'
collect2: error: ld returned 1 exit status

And, I just discovered that running with any optimization magically fixes it:

for i in {0..4}; do echo "=== Attempt $i ==="; $CXX derp.cc -Wall -Wextra -std=c++11 -D ATTEMPT=$i -O2 -o derp.exe && ./derp.exe && echo "SUCCESS" ; done;
=== Attempt 0 ===
SUCCESS
=== Attempt 1 ===
SUCCESS
=== Attempt 2 ===
SUCCESS
=== Attempt 3 ===
SUCCESS
=== Attempt 4 ===
SUCCESS

Am I somehow using unspecified behavior here? Why isn't Args&& passing the int through by value?

Aucun commentaire:

Enregistrer un commentaire