Search code examples
c++c++11header-only

constexpr template meta-info variable in c++11


I'm trying to represent some meta information in a structured way (i.e. using a class). It's header-only and I need to support c++11 so can't use inline variables. I've come up with a couple of potential solutions but each has its drawbacks. Any suggestions would be much appreciated though just pointing on how to make "alternative B" to compile would be a great solution for me.

SthInfo::fieldA is used as an argument to a templated Processor<Type>::work(field) which :

  • checks that the "container type" matches this processor
  • and uses the ID together with other non-relevant args do some internal logic

Alternative A - working but templated ID

#include <type_traits>

template <typename Container, int ID>
class FieldInfo {
public:
    static constexpr int id = ID;
};

template <typename T>
class Processor {
public:
    template <typename Container, int ID>
    void work(FieldInfo<Container, ID> field) {
        static_assert(std::is_same<Container, T>::value, "Given field can't be processed - container type mismatch");
        // some business logic using the ID, i.e. accessing `field.id`
        int id = field.id;
    }
};

struct Sth {/* contains fieldA and fieldB - just as an example */};

// SthInfo holds meta-information about fields in Sth
struct SthInfo {
    static constexpr FieldInfo<Sth, 1> fieldA{};
};

int main() {
    Processor<Sth> processor;
    processor.work(SthInfo::fieldA);
}

This works (compiles and links) fine on Linux and Windows. However, Is there a way to avoid the ID constant in the template and have it as a field in the FieldInfo class? Any other improvement ideas?

Alternative B - broken - won't link

I've tried changing to the following code but it doesn't link on Linux (but does on Windows...) with undefined reference to SthInfo::fieldA:

#include <type_traits>

template <typename Container>
class FieldInfo {
public:
    const int id;
};

template <typename T>
class Processor {
public:
    template <typename Container>
    void work(FieldInfo<Container> field) {
        static_assert(std::is_same<Container, T>::value, "Given field can't be processed - container type mismatch");
        // some business logic using the ID, i.e. accessing `field.id`
        int id = field.id;
    }
};

struct Sth {/* contains fieldA and fieldB - just as an example */};

// SthInfo holds meta-information about fields in Sth
struct SthInfo {
    static constexpr FieldInfo<Sth> fieldA{1};
};

int main() {
    Processor<Sth> processor;
    processor.work(SthInfo::fieldA);
}

Alternative C - constexpr function - not so nice to use.

Changing SthInfo::fieldA to a constexpr function helps but then you have to use () when using in the app code...

#include <type_traits>

template <typename Container>
class FieldInfo {
public:
    const int id;
};

template <typename T>
class Processor {
public:
    template <typename Container>
    void work(FieldInfo<Container> field) {
        static_assert(std::is_same<Container, T>::value, "Given field can't be processed - container type mismatch");
        // some business logic using the ID, i.e. accessing `field.id`
        int id = field.id;
    }
};

struct Sth {/* contains fieldA and fieldB - just as an example */};

// SthInfo holds meta-information about fields in Sth
struct SthInfo {
    static constexpr FieldInfo<Sth> fieldA() { return FieldInfo<Sth>{1}; }
};

int main() {
    Processor<Sth> processor;
    processor.work(SthInfo::fieldA());
}

Solution

  • Prior to C++17, which introduced inline variables—and made constexpr static member variables implicitly inline—you have to define such variables outside the class if they are odr-used:

    const FieldInfo<Sth> SthInfo::fieldA;
    

    Note that this is a non-templated variable and thus must be defined in exactly one source file. Similar definitions of templated variables can appear in headers (using the same compiler/linker support as used for inline variables), so you’ll want to use something like

    template<class T>
    struct Info {
        static constexpr FieldInfo<T> fieldA{1};
    };
    template<class T> const FieldInfo<T> Info<T>::fieldA;