3

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());
}
vaind
  • 1,642
  • 10
  • 19
  • so you just need to change your mind on what is "nice" then you have a nice working solution, no? What is the question? – 463035818_is_not_an_ai Jul 03 '20 at 15:14
  • @idclev463035818 obviously that's true :), but I'm hoping for a more elegant solution - that's why I'm reaching out to the community – vaind Jul 03 '20 at 15:26
  • then you need to explain what you call "more elegant". I am just trying to tease a more specific question out of you ;) – 463035818_is_not_an_ai Jul 03 '20 at 15:27
  • 1
    also it would be good to include [mcve]s. When I see `...` I always get confused (because its either something about variadic templates or incomplete code). If what you have in place of `...` isnt necessary to understand the quesiton you can remove it. If it is necessary you should include it – 463035818_is_not_an_ai Jul 03 '20 at 15:30
  • I cannot reproduce "but it doesn't link on Linux" for B case. Please, can you include a minimal reproducible example. – max66 Jul 03 '20 at 15:36
  • I've changed the description - each tried "solution" is a self-contained main.cpp file – vaind Jul 03 '20 at 16:01
  • yeah and clarified the goal - getting alternative B to compile & link. – vaind Jul 03 '20 at 16:05
  • BTW I've found out AlternativeB links without issues when specifying c++17 but it doesn't for c++11. Trying to figure out what's wrong because I have to target c++11 :/ – vaind Jul 03 '20 at 16:09
  • [OT]: Why making template method when only `T` is accepted? – Jarod42 Jul 03 '20 at 18:05
  • Processor is a templated class @Jarod42, it has some universal logic but needs to make sure the right processor is used with the right type. The names used are rather auxiliary - I've tried to filter out any details not necessary for the question. – vaind Jul 04 '20 at 18:08
  • I meant `template void Processor::work(FieldInfo field)` seems enough, instead of `template template void Processor::work(FieldInfo field) { static_assert(std::is_same_v); }`. – Jarod42 Jul 08 '20 at 18:04
  • Good point, that should be enough and replaces the static_assert(). Would have to check this whether the given compiler error (in case you used an incompatible field as an argument) would be readable. – vaind Jul 10 '20 at 06:29

1 Answers1

1

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;
Davis Herring
  • 36,443
  • 4
  • 48
  • 76
  • Thanks, unfortunately that doesn't work for me since those classes are part of the (generated) header-only code. I've already tried that and can't make it work for c++11 even by using `inline` because that's c++17 only :/ – vaind Jul 04 '20 at 18:03
  • @vaind: I didn’t see any restriction in the question on changing the `SthInfo` class—indeed, you showed three different versions of it. Did I misunderstand, or did I just inadequately emphasize the approach that can be header-only? – Davis Herring Jul 04 '20 at 18:24