0

I want to do something like this:

  • My library defines a struct LibStruct
  • Clients of the library build their own structs that are somehow related (in the example, I used inheritance but maybe there's a better way of expressing)
  • The library has a method which does some operation using these structs
  • Avoid virtual function calls (hard requirement) and avoid union/variants (soft requirement)

What I've been doing so far is this:

template <typename T /** And a bunch more **/>
struct LibStruct {
  using Scalar = T;
  // And a bunch more aliases

  // Some data
};

template <typename T>
struct ClientStuctA : LibStruct<T> {
  using Scalar = T; // Can this be avoided?

  Scalar moreData;
};

struct ClientStuctB : LibStruct<double> {
  using Scalar = double; // Can this be avoided?

  Scalar otherData;
};

template <typename Whatever>
typename Whatever::Scalar doSomething(const Whatever& input) {
  // Do stuff
  return Whatever::Scalar();
}

My issue with this is that all of the client structs need to redefine all the aliases so that doSomething can use them. Is there a way to avoid the need for that?

(Asking for C++14, but if there's a C++17 solution I'll take that too)

digitalPhonix
  • 138
  • 1
  • 7
  • 2
    "Avoid virtual function calls" -- a.k.a. avoid one of the core fundamental components of C++. You might as well write everything in C. – Sam Varshavchik Feb 16 '20 at 22:17
  • 1
    Did you try it? It seems to work fine for me... http://coliru.stacked-crooked.com/a/b510508336848c5c – N. Shead Feb 16 '20 at 22:27
  • 1
    @N.Shead No, it does not: http://coliru.stacked-crooked.com/a/8fe6810dcab3e22d – DeducibleSteak Feb 16 '20 at 22:29
  • 1
    What are your policies on macros? You could just have a macro that does all of the work in one line, and any changes to be made need also be done only once to the definition of the macro. – DeducibleSteak Feb 16 '20 at 22:32
  • 1
    @DeducibleSteak that wasn't the question they were asking. Still, if you read the error message that's easily solvable without redefining the aliases: http://coliru.stacked-crooked.com/a/0b5c0887d7c6b0eb – N. Shead Feb 16 '20 at 22:38
  • 1
    My interpretation of the question is that OP just wants to be able to use the Scalar alias in the body of the inherited structs. Using "typename LibStruct::Scalar" is not an acceptable solution, the way I read the question. I assume OP knows it can be done and rejects the verbosity of the solution. – DeducibleSteak Feb 16 '20 at 22:41
  • @DeducibleSteak - that is correct, I don't want to have to repeat myself everywhere (or have to add new aliases to all the client structs when the base struct has a new one). I haven't really thought about using macros because I'm scared of getting them wrong in subtle ways :P but if you have a suggestion I'm all ears! – digitalPhonix Feb 16 '20 at 22:42
  • @N.Shead - it doesn't compile if you remove the "// Can this be avoided" lines – digitalPhonix Feb 16 '20 at 22:45
  • 1
    I just want to repeat the suggestion of having a single macro full of "using Scalar = typename LibStruct::Scalar;" aliases that you need to insert at the top of every child type. – DeducibleSteak Feb 16 '20 at 22:46
  • 1
    @digitalPhonix http://coliru.stacked-crooked.com/a/3291eb6ede0d0a50 So, for clarification, your issue is _not_ with `doSomething`, but is with the `Scalar moreData` line? If so you should edit your question to make that clear. – N. Shead Feb 16 '20 at 22:46
  • 1
    @DeducibleSteak That won't work if the template names are different, or if it's not templated at all. (It's also not necessary without the template, but things like this are a requirement when in a templated class.) – N. Shead Feb 16 '20 at 22:48
  • @N.Shead - I guess both? I want to make it as simple as possible for someone to make a struct to pass to `doSomething` and I don't want to make `doSomething` unnecessarily verbose. – digitalPhonix Feb 16 '20 at 22:53
  • 1
    @N.Shead I now see how the question was misleading to you, I skipped that sentence and just looked at the code. Yes, in a non templated class it's not even neccessary, and in a templated class you could first make a Base alias and then access that in the macro, but sure, I see how this gets ugly fast. – DeducibleSteak Feb 16 '20 at 22:53
  • Sorry if I'm not explaining the question well - @DeducibleSteak if you've worked out the miscommunication can you help me and edit the question? :) – digitalPhonix Feb 16 '20 at 22:54
  • 1
    Your emphasis on "so that doSomething can use them" lead N.Shead to believe just that was your problem, not also the need to use those aliases in the body of the derived structs directly. – DeducibleSteak Feb 16 '20 at 22:57
  • 1
    @digitalPhonix So this seems related to https://stackoverflow.com/questions/1567730/inheritance-and-templates-in-c-why-are-inherited-members-invisible and https://stackoverflow.com/questions/1643035/propagating-typedef-from-based-to-derived-class-for-template – N. Shead Feb 16 '20 at 23:02
  • 1
    Does this answer your question? [Propagating 'typedef' from based to derived class for 'template'](https://stackoverflow.com/questions/1643035/propagating-typedef-from-based-to-derived-class-for-template) – N. Shead Feb 16 '20 at 23:03
  • @N.Shead - yeah that is basically the same problem (and the answers explain why there isn't a solution) :( I guess the macro solution would be the best workaround... – digitalPhonix Feb 16 '20 at 23:16
  • @DeducibleSteak if you make that into an answer I'll accept it – digitalPhonix Feb 16 '20 at 23:16

1 Answers1

2

So to sum up the back and forth me and @N.Shead had in the comments, supported by these links on why you can't do it better:

Propagating 'typedef' from based to derived class for 'template'

Inheritance and templates in C++ - why are inherited members invisible?

This seems to be the best we can do:

#include <iostream>

template<typename T /* And a bunch more */>
struct LibStruct {
  using Scalar = T;
  // ... and a lot more aliases
};

#define LIBSTRUCT_TYPES(base)           \
  using Scalar = typename base::Scalar; \
  // ... and a lot more

template<typename T>
struct ClientStuctA : LibStruct<T> {
  LIBSTRUCT_TYPES(LibStruct<T>)

  Scalar moreData;
  // OtherTypes moreDefinitions();

  Scalar func1() { return Scalar(2) * 3; }
  Scalar func2();
};

template<typename T>
typename ClientStuctA<T>::Scalar ClientStuctA<T>::func2() {
  return 5;
}

int main() {
  std::cout << ClientStuctA<int>().func1() << std::endl;
  std::cout << ClientStuctA<int>().func2() << std::endl;
  return 0;
}
DeducibleSteak
  • 1,398
  • 11
  • 23