1

Briefly: In a template that takes an enum type as parameter (not a scoped enum, a regular old pre-C++11 enum) I need to call a method, declared in the same struct as the enum, which takes an enum value as its parameter. Is there a way to do this?

In full: Suppose I'm "self-scoping" unscoped enums as follows:

struct W {
   enum E { A, B, C };

   static string foo(enum E e);
};

Now suppose I have a bunch of these struct declarations following this pattern - each has an enum though the enum type's name is different and each has a static method foo taking one argument which is of that enum type.

Now I want to create a template that given a bunch of one of these enum types wants to transform each one according to its own foo():

template <typename E>
vector<string> xform(const vector<E> es) {
  vector<string> ss;
  for (E e : es) {
    ss.push_back(foo(e));
  }
  return ss;
}

Now I instantiate xform:

...
    vector<enum W::A> as{W::A, W::C};
    auto Wx = xform(as);
...

and of course I get a compiler error because the compiler can't find the right foo to call:

prog.cc: In instantiation of 'std::vector<std::__cxx11::basic_string<char> > xform(std::vector<E>) [with E = W::A]':
prog.cc:34:24:   required from here
prog.cc:24:21: error: 'foo' was not declared in this scope
   24 |     ss.push_back(foo(e));
      |                  ~~~^~~

(This is all here on wandbox.)

So I need to get from the enum type to its peer method. Can I do that - how? (Since the peer method's name is always the same that part's easy - I don't know how to get from the enum type to its enclosing type.)

(Obviously I can solve this if the enum type's name in each struct is the same, by using the struct's name as the template argument. But in my use case the enums all have different names.)

Resolution (at this point): D-RAJ's answer though very simple doesn't work in this particular case because of the function foo is not dependent on the type parameter in the template function xform, thus the rules related to lookup and ADL and a non-dependent name in a template mean that ADL can not be used if you try to implicitly instantiate xform in code in some other namespace. (I'm not sure I understand why, but that's the fact.) The error is that the name cannot be found at the point of template instantiation, only at the point of declaration (and only then via ADL). I guess you could get around that with explicit instantiations ... but ...

dxiv's answer of using traits works great, is not onorous, and it can be done without modifying the existing wrapped-enums at all.

davidbak
  • 5,775
  • 3
  • 34
  • 50

2 Answers2

4

In simple, No, you cant do it like that. There are 2 reasons for this,

  1. vector<enum W::A> and template <typename E> vector<string> xform(const vector<E> es) doesn't let you find which struct contains the function foo(). This is because the std::vector::_Ty = enum W::A which is basically an enum type. How can you find a struct from an enum type?
  2. When you define the static foo() function in a struct or class, the function is in the scope of the struct/ class (its the same as namespace W { string foo(...) { ... } }). This is the reason for this error: prog.cc:24:21: error: 'foo' was not declared in this scope.

How to resolve this issue?
The only reasonable option that comes to my mind is to put the foo() function in the global scope and provide overrides to it. It would look something like this.

struct W1 {
    enum A { A, B, C };
};

string foo(enum W1::A a) { return std::array{ "A","B","C" } [a] ; }

struct W2 {
    enum B { X, Y, Z, };
};

string foo(enum W2::B b) { return std::array{ "X", "Y", "Z" } [b] ; }

Now the xform() function can resolve which foo() to be used at compile time without any scope issues.

D-RAJ
  • 3,263
  • 2
  • 6
  • 24
  • 1
    I'd suggest free function as you did and local `static std::array` inside `foo` – Moia Jan 15 '21 at 07:29
  • This is a good suggestion - especially if declared as friends (I think) - and they needn't be truly global either if all of these wrapped-enum types were in the same namespace ... – davidbak Jan 15 '21 at 17:38
  • BTW, the whole question is your point #1: How do you find a struct from an enum type? You provided a good workaround, which satsifies me, but ... technically speaking, didn't answer how you'd do that in C++ or provide some language-related argument why you can't! I can't think of a way but there might be one given all the nearly-magically template metaprogramming around, especially with the seemingly hundreds of type-traits in the standard ... – davidbak Jan 15 '21 at 17:55
  • @davidbak As for my knowledge and correct me if I'm wrong, I don't think there's a way. Once you pass `enum W1::A` to the `std::vector`, the type is `enum W1::A`. This means that `E` in `xform()` will be an `enum` type. There isn't a way (in type_traits) in which we can accomplish without a workaround. [Reference](https://en.cppreference.com/w/cpp/header/type_traits) – D-RAJ Jan 15 '21 at 19:11
1

I don't know that it's possible as stated, and don't think it is.

A relatively cheap alternative could be to maintain the association between names by hand. In the sample code below, this is done by specializations of a helper template WX<> (tried here).

#include <string>
#include <vector>
using std::string;
using std::vector;

struct W1 {
  enum E1 { A, B, C };
  static string foo(enum E1 e);
};

struct W2 {
  enum E2 { A, B, C };
  static string foo(enum E2 e);
};

template<typename T> struct WX;
template<> struct WX<W1::E1> { using W = W1; };
template<> struct WX<W2::E2> { using W = W2; };

template<typename E> vector<string> xform(const vector<E> es) {
  vector<string> ss;
  for (E e : es) {
    ss.push_back(WX<E>::W::foo(e));
  }
  return ss;
}

void bar()
{
  vector<enum W1::E1> a1s { W1::A, W1::C };
  auto w1x = xform(a1s);
  vector<enum W2::E2> a2s { W2::A, W2::C };
  auto w2x = xform(a2s);
}
dxiv
  • 16,984
  • 2
  • 27
  • 49
  • Why runtime containers? Why templates if an simple overload is enough? – Klaus Jan 15 '21 at 07:15
  • 2
    @Klaus There are no "*runtime containers*" in the code above (other than the original vectors from OP's example, of course). And this approach requires no changes to OP's existing classes, unlike overloads. – dxiv Jan 15 '21 at 07:17
  • std::vector is runtime! You have to fill the container at runtime while a std::array is "filled" at compile time. In comparison with the other answer on this thread it takes lot more code and runtime. To complicated in my eyes... it will work but is far away from optimal. Only my two cents – Klaus Jan 15 '21 at 07:20
  • 2
    @Klaus The only `std::vector` in my code comes straight from the OP, and is not related to the technique itself, anyway. Please read the original post again, including OP's code snippets. What my answer does is make OP's code work, with a minimum of plumbing and no redesign. – dxiv Jan 15 '21 at 07:23
  • 2
    I disagree :-) OP has a problem to solve. OP provides an idea, which is also far away from optimal. So it feels good to provide a much better solution without the memory and runtime overhead, because it is not needed. And dealing with all the template stuff without any real use case? Why? We see often inexperienced users to use templates instead of a simple overload. So it is a good idea to remove unneeded templates if it fits to the problem and reduce complexity. – Klaus Jan 15 '21 at 07:27
  • 1
    @Klaus In this case templates do nothing more than automate the definition of those overloads which you propose to do by hand. I suggest you post your own answer based on overloads and compare. Just make sure it compiles OP's particular `xform` so that it's apples to apples. – dxiv Jan 15 '21 at 07:33
  • "In this case templates do nothing more than automate the definition of those overloads " Exactly this is not true: You create a template and must instantiate a lot of instances by hand! Again: What helps a template if I need to specify explicitly the template parms everywhere. Again: Not needed. A template is perfect if it helps. Here it is an unneeded overhead. It requires the user to write more code as without the template and the template itself must be written Why? Nothing is better with the template as without. I want only to spend you an idea why I dislike your solution.:-) – Klaus Jan 15 '21 at 07:43
  • 1
    @Klaus You are barking up the wrong tree. This `WX<>` helper template has exactly zero footprint in the compiled code, and an overhead of one line per class in the source. That said, it's perfectly fine if you don't like templates, equally fine if you have a superior solution, and even better if you post it. Bye now. – dxiv Jan 15 '21 at 07:56
  • std::vector needs runtime and code. The template needs source lines without any sense and increase complexity. Quite interesting to argue that the template takes no footprint :-) I wonder... :-) and I am not "barking" Sir! I criticize your solution to give YOU an idea what I think can be improved. Others downvote without any comment... – Klaus Jan 15 '21 at 08:11
  • 1
    @Klaus OP did give an example using `std::vector`, and highlighted the problem as to find the correct `foo` overload; so what container type, or if we even use a container type here, is arguably irrelevant. In those cases, I would prefer either prompting OP to minimize the example to remove noise, or to answer the question as dxiv has done here: using the same form as the example of the OP. And then back to the actual question: I think a typemap using a specialized class template, as in this example, is an excellent alternative to breaking out separate overloads (+1). ... – dfrib Jan 15 '21 at 08:25
  • 1
    ... Both approaches need some manual maintenance for writing either the type map or the separate overload (and the compiler will help us if we forget an overload / a type map), and generally (as always) the simpler approach is arguably preferable (namely overloads). However, in certain scenarios you want to tie the overload to the type as above, using a semantic interface (no enforced by the compiler) e.g. for static dependency injection elsewhere. In those kind of cases, this answer provides a viable alternative approach (as compared to breaking out the functions from the "parent" types). – dfrib Jan 15 '21 at 08:27
  • This is using traits, is it not? For my use case it will work fine - I can easily add the traits for the fixed set of wrapped-enum types I'm thinking of. – davidbak Jan 15 '21 at 17:40
  • I wish an argument hadn't broken out here, but in fact it is correct that I'm not interested at all in what the foo method _does_ - I just had to do something to demonstrate things and chose selecting an element out of an array. The question was related to how to find the foo method. – davidbak Jan 15 '21 at 17:42
  • @davidbak Guess it could be called traits, or type traits, insofar as it manipulates types. And also because [traits](https://stackoverflow.com/questions/6718654/what-kind-of-traits-are-used-defined-in-the-c0x-standard) is a wide enough umbrella to cover many different things. That's probably one reason why [type traits](https://www.stroustrup.com/C++11FAQ.html#type_traits) in the C++ FAQ says just "*Sorry. Come back later*" ;-) On the other hand [template alias](https://www.stroustrup.com/C++11FAQ.html#template-alias) in the same FAQ uses a technique similar to the above... – dxiv Jan 15 '21 at 22:11
  • ...@dfrib's previous comment called it a "typemap", and I find that description to be the most descriptive, since that's precisely what it does - define an association between types. However, I haven't seen that term too widely used. – dxiv Jan 15 '21 at 22:12