Question

In the following, static constexpr member L is initialized in-class A and then passed by value or by (universal) reference. The latter fails in Clang but not in GCC, and behaviour is slightly different for member/non-member functions. In more detail:

#include <iostream>

using namespace std;

struct A
{
    static constexpr size_t L = 4;

    template <typename T>
    void member_ref(T&& x) { cout << std::forward<T>(x) << endl; }

    template <typename T>
    void member_val(T x) { cout << x << endl; }
};

template <typename T>
void ref(T&& x) { cout << std::forward<T>(x) << endl; }

template <typename T>
void val(T x) { cout << x << endl; }

int main ()
{
    A().member_ref(A::L);  // Clang: linker error: undefined reference to `A::L'
    A().member_val(A::L);  // fine (prints 4)
    ref(A::L);             // Clang: compiles/links fine, no output
    val(A::L);             // fine (prints 4)
}

After some experimentation in isolating the problem from a larger program, I realized that I am accidentally using the address of a constexpr variable, although I am only interested in the value.

I want to pass by (universal) reference so that the code is generic and works with large structures without copying. I thought that you could pass anything with a universal reference but it appears this is not the case here. I cannot use a separate (out-of-class) definition for L because this is part of a header-only library.

So one workaround can be to generate a value upon call, i.e. say size_t(A::L) or something like A::get_L() instead of just A::L, where (within class A)

static constexpr size_t get_L() { return L; }

but both solutions look a bit clumsy. In my actual code the call is made within the class and looks like call(0, L, ...) which appears quite innocent (0, L look like values). I'd like to keep the call as simple as possible.

I think this question and its follow-up pretty much explain what is happening. So could anyone suggest what would be the cleanest way to deal with this?

Was it helpful?

Solution

You need to define A::L outside its class in a source file

constexpr size_t A::L;

Live example using Clang

For header-only code, and if your class A is not already a template, you can define a class template A_<T> with a void default value, and write a typedef for A in terms of that

template<class = void>
struct A_
{
    static constexpr size_t L = 4;

    template <typename T>
    void member_ref(T&& x) { cout << std::forward<T>(x) << endl; }

    template <typename T>
    void member_val(T x) { cout << x << endl; }

};

template<class T>
constexpr size_t A_<T>::L;

using A = A_<>;

Live Example.

NOTE: this business can involve a fair amount of boiler-plate. It is good to note that one can write

template
<
    class MyConcept1, 
    class MyConcept2, 
    class YetAnotherConcept
    // more long and well-documented template parameter names
>
struct A
{
    // many static constexpr variabels
};

template<class P1, class P2, class P3 /* many more short parameter names */>
constexpr SomeType A<P1, P2, P3, /* many more */>::some_var;

// many more one-liners.

Template parameters just have formal names, they don't have to be the same everywhere (just put them in the right order everywhere, though!).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top