Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

passing a static constexpr variable by universal reference?

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?

like image 658
iavr Avatar asked Oct 02 '22 10:10

iavr


1 Answers

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

like image 118
TemplateRex Avatar answered Oct 17 '22 06:10

TemplateRex