Home > Software design >  Why can't I wrap a template parameter with parentheses?
Why can't I wrap a template parameter with parentheses?

Time:02-06

to avoid XY, I will start by explaining my overall goal.

I'm trying to make a choice between two different generic containers at compile-time. The solutions I came up with is very straightforward using macros. For the sake of demonstaration here is how it would look with std::vector and std::set (in practice they're other containers but it's irrelevant to the issue)

// switch between the container types
//#define CONT_SET

#ifdef CONT_SET
#define CONTAINER(type) std::set<type>
#else
#define CONTAINER(type) std::vector<type>
#endif

int main() {
    CONTAINER(float) cont;    
}

this works perfectly fine.
The problem arises when I try to store more complicated types in the container, for example

CONTAINER(std::pair<int, int>) cont;

this will not work, because the compiler detects this as two different macro parameters std::pair<int and int>.
I tried overcoming this issue by adding parantheses that group the entire type together

CONTAINER((std::pair<int, int>)) cont;

but then I get a 'template argument 1 is invalid' (godbolt)

is there a way to tell the compiler that the entire expression is just one macro parameter? or that the parenthesized template parameter is a valid type?

CodePudding user response:

Premised that I think that C/C macros are distilled evil (and that seems to me that you can substitute Container() using using) you can pass throug a type alias

using pair_i_i = std::pair<int, int>;

CONTAINER(pair_i_i) cont;

CodePudding user response:

To the preprocessor, std::pair<int, int> looks like two template arguments, std::pair < int and int > because the comma isn't guarded. But also extra parentheses generally aren't allowed in type names, so (std::pair<int, int>) is not syntatically valid.

There are a few way to remedy this:

  • If your type is the last argument of your function macro (like it is in your case), you can make it variadic:
#define CONTAINER(...) std::vector< __VA_ARGS__ >
  • You can use a comma macro that you don't need to guard:
#define COMMA ,

CONTAINER(std::pair<int COMMA int>) cont;
  • You can introduce a type alias that doesn't have commas
using cont_value = std::pair<int, int>;

CONTAINER(cont_value) cont;
  • You can introduce parentheses to guard the comma in a different way (here I use the parentheses in decltype(...))
#define GUARD_TYPE_NAME(...) typename decltype(std::type_identity< __VA_ARGS__ >())::type

CONTAINER(GUARD_TYPE_NAME(std::pair<int, int>)) cont;

CodePudding user response:

I think that a cleaner solution is to encapsulate whatever you're doing inside your own type:

template <typename type> 
class my_container
{
#ifdef CONT_SET
    std::set<type> cont;
#else
    std::vector<type> cont;
#endif
};

int main() {
    my_container<int> a;
}

CodePudding user response:

How about this?

// switch between the container types
//#define CONT_SET

#ifdef CONT_SET
template <typename T>
using container = std::set<T>;
#else
template <typename T>
using container = std::vector<T>;
#endif

That reduces the use of macros, which is always good.

CodePudding user response:

The most common way to get around this problem is by aliasing using using as the other answers mentioned.

However, here is another way using template specialization if you want to avoid using parameterized macros and also be able to make some of your Container variables independent of CONT_SET.

#define CONT_SET

#ifdef CONT_SET
#define MYTYPE signed  // selects set.
#else
#define MYTYPE unsigned  // selects vector.
#endif

#define SET signed     // selects set.
#define VECTOR signed  // selects vector.

// General case
template <typename T1, typename T2>
class Container {};

// Specialized when T2=unsigned, selects vector.
template <typename T1>
class Container<T1, unsigned> {
 public:
  std::vector<T1> c;
};

// Specialized. When T2=signed, selects set.
template <typename T1>
class Container<T1, signed> {
 public:
  std::set<T1> c;
};

int main() {
  // This one uses set or vector depending on CONT_SET
  Container<int, MYTYPE> my_cont;

  // This one uses set
  Container<int, SET> set_cont;

  // This one uses vector
  Container<int, VECTOR> vector_cont;
  return 0;
}

https://godbolt.org/z/q9W39s7qx

  •  Tags:  
  • Related