8

I ran into this problem earlier today. In the following code:

template <int> struct Holder {};

template <typename> struct Helper { using T = Holder<__COUNTER__>; };  // ???

int main() {
  auto a = typename Helper<bool>::T();
  auto b = typename Helper<int>::T();

  std::cout << (typeid(a) == typeid(b)) << std::endl;
  return 0;
}

When compiled and executed with:

g++ test.cpp -std=c++11 -o test
./test

It prints out 1 instead of 0, meaning that the 2 Ts in Helper<int> and Helper<bool> are the same type, which makes me wonder:

  1. Why the line marked with // ??? is executed only once instead of once for each of the type?
  2. Is there a way to force the line to be executed once for each of the type and preferably without modifying the definition of Holder?

==================================================== Clarifications:

The (closer to) real scenario is:

  1. The struct Holder is defined in a header from a third-party library. The type for the struct is actually very complicated and the library writer provides users with another macro:
template <bool, int> struct Holder {};

#define DEF_HOLDER(b)  Holder<b, __COUNTER__>()

At some point of the program, I want to take a "snapshot" of the type with current counter by aliasing the type so that it could be used in a function:

template <bool b>
struct Helper { using T = decltype(DEF_HOLDER(b)); };

template <bool b, typename R = typename Helper<b>::T>
R Func() {
  return R();
}

// Note that the following does not work:
// Since the 2 types generated by DEF_HOLDER do not match.
template <bool b>
auto Func() -> decltype(DEF_HOLDER(b)) {
  return DEF_HOLDER(b);
}

The problem here is that the following 2 usage has inconsistent semantics as illustrated:

int main() {
  auto a = DEF_HOLDER(true);
  auto b = DEF_HOLDER(true);
  auto c = Func<true>();
  auto d = Func<true>();

  std::cout << (typeid(a) == typeid(b)) << std::endl;  // prints 0
  std::cout << (typeid(c) == typeid(d)) << std::endl;  // prints 1

  return 0;
}

In my use case, it is important for multiple invocation of Func to return different types as it does with invoking DEF_HOLDER directly.

kkspeed
  • 373
  • 2
  • 9
  • 1
    Even though it prints 1 it does not mean that `Helper` and `Helper` are the same type. They are different types `static_assert(false == ::std::is_same_v, Helper>);`. It is not clear what are you trying to achieve here. – user7860670 Feb 06 '19 at 07:40
  • 3
    @VTT - The OP is asking about `Helper::T` and `Helper::T` - which are the same type here. – StoryTeller - Unslander Monica Feb 06 '19 at 07:51
  • 1
    @StoryTeller Yes, but if OP wants to get two different types from the `Helper` and `Helper` he can use these types themselves. Or make something like `struct T{};` instead of `using T = Holder<__COUNTER__>;`. So it is not clear why would he need that `Holder` type. – user7860670 Feb 06 '19 at 07:56
  • @VTT - I imagine this is some sort of simplified form of an attempt at meta-programming. I agree that this might be an XY problem though. – StoryTeller - Unslander Monica Feb 06 '19 at 07:57
  • @kkspeed - Instead of fixating on question 2, which is what you feel is the solution to your actual problem, would you mind telling us a thing or two about your end goal? Like I said already, this has the feeling of an [XY question](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem) about it. – StoryTeller - Unslander Monica Feb 06 '19 at 08:00
  • @StoryTeller - Please see the clarification. Sry for the confusion. – kkspeed Feb 06 '19 at 08:52
  • Now this makes even less sense. You write *"I want to take a "snapshot" of the type with current counter by aliasing the type so that it could be used in a function"* bun then you complain that returning that snapshotted type from a function yields the same type. – user7860670 Feb 06 '19 at 09:22
  • You simply cannot encapsulate a context dependent macro in a function, type, template, or anything else except possibly another macro. – n. m. could be an AI Feb 06 '19 at 10:06
  • Possible duplicate of [Does C++ support compile-time counters?](https://stackoverflow.com/questions/6166337/does-c-support-compile-time-counters) – Arne Vogel Feb 06 '19 at 11:19

2 Answers2

13

The symbol __COUNTER__ is a preprocessor macro, it's expanded once only.

That means T will always be Holder<0> (since __COUNTER__ starts at zero), no matter the type used for the template Helper.

See e.g. this GCC predefined macro reference for more information about __COUNTER__.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • Thanks. Please see my clarification. Is there a way to force multiple expansion in this case? – kkspeed Feb 06 '19 at 08:51
  • 1
    @kkspeed: Only this way, which is fragile, not standard conforming, difficult to understand and fun ; ): http://b.atch.se/posts/non-constant-constant-expressions/ Here is a standard committee voice over this technique: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#2118 – Michał Łoś Feb 06 '19 at 09:04
0

I am not sure whether I completely understand the problem, but since C++14 there is no need to use DEF_HOLDER two times. The following code also works:

template <bool b>
auto Func() {
   return DEF_HOLDER(b);
}

If you want a different type for every function call, you can add the int parameter:

template <bool b, int i>
auto Func()
{
  return Holder<b, i>();
}

You could hide this int in a macro:

#define FUNC(b)  Func<b,__COUNTER__>();

Then a,b and c,d have the same semantics:

int main() {
  auto a = DEF_HOLDER(true);
  auto b = DEF_HOLDER(true);
  auto c = FUNC(true);
  auto d = FUNC(true);

  std::cout << (typeid(a) == typeid(b)) << std::endl;  // prints 0
  std::cout << (typeid(c) == typeid(d)) << std::endl;  // prints 0

  return 0;
}
Helmut Zeisel
  • 388
  • 1
  • 10