Trouble with template parameters used in macros
Asked Answered
F

8

28

I'm trying to compile the following piece of code, I get an error on the line which specializes std::vector, it seems the one parameter being passed-in is somehow being assumed to be two parameters. Is it perhaps something to do with angle-brackets?

Is there a special way/mechanism where by such parameters can be correctly passed to the macro?

#include <vector>

template<typename A>
struct AClass {};

#define specialize_AClass(X)\
template<> struct AClass<X> { X a; };


specialize_AClass(int) //ok

specialize_AClass(std::vector<int,std::allocator<int> >) //error

int main()
{
   return 0;
}

The error that I get is as follows:

1 Line 55: error: macro "specialize_AClass" passed 2 arguments, but takes just 1
2 Line 15: error: expected constructor, destructor, or type conversion before 'int'
3 compilation terminated due to -Wfatal-errors.

Link: http://codepad.org/qIiKsw4l

Fard answered 28/11, 2010 at 7:57 Comment(0)
A
16
template<typename TypeX, typename TypeY>
class Test
{
public:
    void fun(TypeX x, TypeY y)
    {
        std::wcout << _T("Hello") << std::endl;
        std::wcout << x << std::endl;
        std::wcout << y << std::endl;
    }
};

#define COMMA ,

#define KK(x) x val;

void main()
{
    KK(Test<int COMMA int>);
    val.fun(12, 13);
}

I have a new way to solve this trouble. hope it can help you :)

Abbe answered 17/2, 2011 at 2:48 Comment(1)
Offends my sense of beauty, but fits the bill. Thanks!Moriyama
A
12

You have two options. One of which was mentioned already: Using __VA_ARGS__. This however has the disadvantage that it doesn't work in strict C++03, but requires a sufficiently C99/C++0x compatible preprocessor.

The other option is to parenthesize the type-name. But unlike another answer claims, it's not as easy as just parenthesizing the type name. Writing a specialization as follows is ill-formed

// error, NOT valid!
template<> struct AClass<(int)> { X a; };

I have worked around this (and boost probably uses the same under the hood) by passing the type name in parentheses, and then building up a function type out of it

template<typename T> struct get_first_param;
template<typename R, typename P1> struct get_first_param<R(P1)> {
  typedef P1 type;
};

With that, get_first_param<void(X)>::type denotes the type X. Now you can rewrite your macro to

#define specialize_AClass(X) \
template<> struct AClass<get_first_param<void X>::type> { 
  get_first_param<void X>::type a; 
};

And you just need to pass the type wrapped in parentheses.

Achorn answered 28/11, 2010 at 19:1 Comment(2)
Alternatively, he could just use BOOST_PP_COMMA() or a similar function to pass in his own commas.Rossner
Great trick! Do you know if there is any way to make this work with cv-qualified types?Jessee
B
8

There is a couple of issues here.

First of all, macros are extremely dumb, they're complicated, but essentially amounts to a pure text replacement processus.

There are therefore 2 (technical) issues with the code you exposed:

  1. You cannot use a comma in the middle of a macro invocation, it just fails, BOOST_FOREACH is a well-known library and yet the only thing they could do was to told the user that it's arguments should not contain commas, unless they could be wrapped in parenthesis, which is not always the case
  2. Even if the replacement occurred, your code would fail in C++03, because it would create a >> symbol at the end of the template specialization, which would not be parsed correctly.

There are preprocessing / template metaprogramming tricks, however the simpler solution is to use a type without commas:

typedef std::vector<int, std::allocator<int> > FooVector;
specialize_AClass(FooVector)

Finally, there is an aesthetic issue, because of their pervasiveness, macros are best given names that cannot possibly clash with "regular" (types, functions, variables) names. The consensus is usually to use all upper case identifiers, like in:

SPECIALIZE_ACLASS

Note that this cannot begin by an underscore, because the standard restricts the use of identifiers matching _[A-Z].* or [^_]*__.* to the compiler writers for the standard library or whatever they feel like (those are not smileys :p)

Bankroll answered 28/11, 2010 at 20:28 Comment(0)
H
3

Since the preprocessor runs before semantic analysis, the comma in your template parameter is being interpreted as the argument separator for the macro. Instead, you should be able to use variadic macros to do something like this:

#define specialize_AClass(...)\
template<> struct AClass< __VA_ARGS__ > { X a; };
Hebraic answered 28/11, 2010 at 8:1 Comment(2)
Note that variadic macros are not actually part of the C++ language prior to C++0x.Elysium
True, but they are part of the C99 standard, and most compilers support them when compiling outside of any pedantic mode.Hebraic
D
1

If you are willing to add a little more code before calling your macro, you could always do this as a workaround:

typedef std::vector<int,std::allocator<int> > myTypeDef; 
specialize_AClass(myTypeDef) //works
Demount answered 2/2, 2013 at 17:47 Comment(1)
You cannot always use typedef. For example, imagine that you want to explicitly instantiate the template class which is passed as macro argument. In such case you need the exact class name, typedef-ed pseudonym won't work.Viva
K
1
#define EMPTY()

#define DEFER( ...  ) __VA_ARGS__  EMPTY()

specialize_AClass( DEFER (std::vector<int,std::allocator<int> >) )
Kinetics answered 2/8, 2022 at 13:1 Comment(1)
Your answer could be improved by adding some explanation. E.g. about what is the order of macro substitution here and why is EMPTY() needed.Impressive
O
0

For simple things you can use typedef

#include <vector>

template<typename A>
struct AClass {};

#define specialize_AClass(X)\
template<> struct AClass<X> { X a; };


specialize_AClass(int) //ok

typedef std::vector<int,std::allocator<int>> AllocsVector;
specialize_AClass(AllocsVector) //ok

int main()
{
   return 0;
}
Outburst answered 26/8, 2022 at 13:35 Comment(0)
H
-3

There are lots of other problems with your code, but to address the specific question, the preprocessor just treats < and > as less-than and greater-than operators.

That's the extent of its knowledge about C++.

There are some tricks that can be used to allow template expressions to be passed as macro arguments, but the simple and by an extremely large margin best answer for a beginner is:

DON'T DO THAT.

Cheers & hth.,

Haematothermal answered 28/11, 2010 at 8:3 Comment(3)
Down voted because (a) "DON'T DO THAT" is not helpful when there are legitimate reasons to do that and when no alternative is given (b) There are good tricks to do that, specified by Johannes above.Halbert
+1 Don't do that in that style would have served better in the comment. But then, I'm not too sure as to how much addressing the OP as opposed to the question deserves to be in the answer. I've leaned towards avoiding addressing the actual OP, because answers should be useful to all people. However, the general case of "don't do that" when concerning macros is appropriate, as it takes a lot of experience to avoid mucking it up, and the person who maintains after you may not have that experience. I've taken the case that a macro should serve as an expression, and not a mask for the language.Unorthodox
U nexpected B ehavior!Unorthodox

© 2022 - 2024 — McMap. All rights reserved.