1

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 6 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;
};

using Baz = std::integral_constant<int, 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
#elif ATTEMPT == 5
    int* v = make(Baz::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
=== Attempt 5 ===
SUCCESS

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
=== Attempt 5 ===
SUCCESS

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

Seth Johnson
  • 14,762
  • 6
  • 59
  • 85
  • Long story short, the reference has to bind to *something*, which requires a definition. However, the optimizer can inline everything and remove the external reference altogether. – Quentin Jun 04 '18 at 14:38
  • Does std::integral_constant work just because it's templated and therefore its symbols get inserted into every translation unit? – Seth Johnson Jun 04 '18 at 15:00
  • 1
    That sounds right, yes. `static` member variables inside templates were a common workaround before inline variables were standardized, and they behave roughly the same. – Quentin Jun 04 '18 at 15:06
  • Possible duplicate of [Undefined reference to static class member](https://stackoverflow.com/questions/272900/undefined-reference-to-static-class-member) – Seth Johnson Jun 06 '18 at 21:15
  • And *now* I am able to find where similar questions were on SO. Marking as a duplicate (mostly) of https://stackoverflow.com/questions/272900/undefined-reference-to-static-class-member . – Seth Johnson Jun 06 '18 at 21:16

0 Answers0