Conditional compile-time inclusion/exclusion of code based on template argument(s)?
Asked Answered
D

5

26

Consider the following class, with the inner struct Y being used as a type, eg. in templates, later on:

template<int I>
class X{
  template<class T1>
  struct Y{};

  template<class T1, class T2>
  struct Y{};
};

Now, this example will obviously not compile, with the error that the second X<I>::Y has already been defined or that it has too many template parameters.
I'd like to resolve that without (extra) partial specialization, since the int I parameter isn't the only one and the position of it can differ in different partial specializations (my actual struct looks more like this, the above is just for simplicity of the question), so I'd like one class fits every I solution.


My first thought was obviously enable_if, but that seems to fail on me, eg. I still get the same errors:

// assuming C++11 support, else use boost
#include <type_traits>

template<int I>
class X{
  template<class T1, class = std::enable_if<I==1>::type>
  struct Y{};

  template<class T1, class T2, class = std::enable_if<I==2>::type>
  struct Y{};
};

So, since enable_if fails, I hope there is another way to achieve the following compile time check:

template<int I>
class X{
  __include_if(I == 1){
    template<class T1>
    struct Y{};
  }

  __include_if(I == 2){
    template<class T1, class T2>
    struct Y{};
  }
};

It would just be to save me a lot of code duplication, but I'd be really happy if it was somehow possible.
Edit: Sadly, I can't use the obvious: variadic templates, as I'm using Visual Studio 2010, so only the C++0x stuff that is supported there I can use. :/

Duodenitis answered 14/4, 2011 at 5:46 Comment(10)
+1. Interesting question. Will try to answer it after office time :DHispaniola
I'm awaiting it with pleasure. :) My line of thought is that it should be possible as the compiler knows everything it needs to know at, well, compile-time.Duodenitis
@Xeo: Are you allowed to use C++0x feautures?Hispaniola
@Xeo: sorry it seems silly, but what of variadic templates ? You could just static_assert the pack size.Deutoplasm
@Matthieu: Not silly, sorry I didn't provide that information: Can't use C++0x's variadic templates of yet, as I'm using Visual Studio 2010. :/ I really hope for VC11 to come out soon, as those variadic templates are just plain awesome for stuff like this and the passkey idiom.Duodenitis
@Nawaz: See my comment above (damn single response notifications. :P).Duodenitis
You are failing in the same trap that many new users, and ask about the solution rather than the problem. What is it that you are trying to solve? How is this class to be used: provide a couple of use case examples of how the class is to be used and why that requires the use of a inner type (have you considered typetraits?) That will make it simpler to argue about potential different approaches.Unfeeling
In the enable_if approach you are missing a couple of :, I hope that was not the source of the errorUnfeeling
@David: Woops, nope, that was just me not looking while typing up an example in the question.Duodenitis
@David: My current intent is to make a template typedef, in a non-C++0x way, or like a general rebind structure like known from allocators. But the question goes farther, as in asking about conditional compilation based on template args in general.Duodenitis
H
9

There are two problems here:

  1. enable_if works with partial specialization, not primary templates.
  2. The number of externally-visible arguments is determined by the primary template, of which there may be only one.

Answer 1.

As you suggested in chat, a linked list of templates can emulate the variadic parameter pack.

template<int I>
class X{
  template<class list, class = void>
  struct Y;

  template<class list>
  struct Y< list, typename std::enable_if<I==1>::type > {
      typedef typename list::type t1;
  };

  template<class list>
  struct Y< list, typename std::enable_if<I==2>::type > {
      typedef typename list::type t1;
      typedef typename list::next::type t2;
  };
};

If you end up with next::next::next garbage, it's easy to write a metafunction, or use Boost MPL.


Answer 2.

The different-arity templates can be named similarly but still stay distinct if they are nested inside the SFINAE-controlled type.

template<int I>
class X{
  template<typename = void, typename = void>
  struct Z;

  template<typename v>
  struct Z< v, typename std::enable_if<I==1>::type > {
      template<class T1>
      struct Y{};
  };

  template<typename v>
  struct Z< v, typename std::enable_if<I==2>::type > {
      template<class T1, class T2>
      struct Y{};
  };
};

X<1>::Z<>::Y< int > a;
X<2>::Z<>::Y< char, double > b;
Hoshi answered 9/6, 2011 at 5:12 Comment(2)
Sir can you please explain your line "enable_if works with partial specialization, not primary templates." , why one can't use it with primary template just like OP did? Thanks a lot :)Dematerialize
@AngelusMortis Partial specialization template parameters are deduced (§14.5.5.1 [temp.class.spec.match]). enable_if works there because Substitution Failure Is Not An Error (SFINAE) within deduction. Primary templates don't have that benefit — the substitution failure is an error.Hoshi
K
3

Here you go:

http://ideone.com/AdEfl

And the code:

#include <iostream>

template <int I>
struct Traits
{
  struct inner{};
};

template <>
struct Traits<1>
{
  struct inner{
    template<class T1>
    struct impl{
      impl() { std::cout << "impl<T1>" << std::endl; }
    };
  };
};

template <>
struct Traits<2>
{
  struct inner{
    template<class T1, class T2>
    struct impl{
      impl() { std::cout << "impl<T1, T2>" << std::endl; }
    };
  };
};

template<class T>
struct Test{};

template<class T, class K>
struct Foo{};

template<int I>
struct arg{};

template<
  template<class, class> class T,
  class P1, int I
>
struct Test< T<P1, arg<I> > >{
  typedef typename Traits<I>::inner inner;      
};

template<
  template<class, class> class T,
  class P2, int I
>
struct Test< T<arg<I>, P2 > >{
  typedef typename Traits<I>::inner inner;      
};

// and a bunch of other partial specializations

int main(){

  typename Test<Foo<int, arg<1> > >::inner::impl<int> b;
  typename Test<Foo<int, arg<2> > >::inner::impl<int, double> c;
}

Explanation: Basically it's an extension of the idea of partial specialization, however the difference is that rather than specializing within Test, delegate to a specific class that can be specialized on I alone. That way you only need to define versions of inner for each I once. Then multiple specializations of Test can re-use. The inner holder is used to make the typedef in the Test class easier to handle.

EDIT: here is a test case that shows what happens if you pass in the wrong number of template arguments: http://ideone.com/QzgNP

Kolivas answered 14/4, 2011 at 8:9 Comment(3)
I have posted the same simplified code, but questioner is saying that template will not be as simple as template<int I>.Alyss
@iammilind, I said it's an extension, the code is slightly different, in the above, the specialization of I is delegated to the Traits class, and the Test class uses that, in your answer, you were talking about specializing Test (or X) for each I - which the OP is not willing to do.Kolivas
you are correct. I am sorry, I missed that. Thanks for poining.Alyss
A
1

Can you try below (it is not partial specialization):

template<int I>
class X
{
};

template<>
class X<1>
{
  template<class T1>
  struct Y{};
};

template<>
class X<2>
{
  template<class T1, class T2>
  struct Y{};
};

I doubt if the answer is that simple !!

Edit (Mocking Partial specialization): @Xeo, I was able to compile following code and seems to be fullfilling.

template<int I>
struct X
{
  struct Unused {};  // this mocking structure will never be used

  template<class T1, class T2 = Unused>  // if 2 params passed-->ok; else default='Unused'
  struct Y{};

  template<class T1> 
  struct Y<T1, Unused>{}; // This is specialization of above, define it your way
};

int main()
{
  X<1>::Y<int> o1;  // Y<T1 = int, T2 = Unused> called
  X<2>::Y<int, float> o2; // Y<T1 = int, T2 = float> called
}

Here, however you can use X<1>, X<2> interchangeably. But in the broader example you mentioned, that is irrelevant. Still if you need, you can put checks for I = 1 and I = 2.

Alyss answered 14/4, 2011 at 6:20 Comment(2)
That's what I meant with "without (extra) partial specialization", as the int I isn't the only template parameter, but there are other. I linked to Ideone for a small example of what it really looks like, the struct was dumbed down for simplicity of the question.Duodenitis
Can you check the edited version ? There actually I have put 2nd template version as main version and 1st version is specialization of 2nd one. Hope it helps!Alyss
K
0

How about this approach - http://sergey-miryanov.blogspot.com/2009/03/template-class-overriding.html? (sorry for russian)

Kannan answered 14/4, 2011 at 6:9 Comment(3)
Sadly not applicable, since the inner template struct should be used as a type, eg in templates. :/Duodenitis
Btw, here is the google-translated site, which brings the intent across just fine.Duodenitis
On a second thought, maybe that would work with some decltype trickery. Will try out once I get home. :)Duodenitis
C
0

You can use a meta function (here: inlined boost::mpl::if_c, but could be arbitrarily complex) to select the one you want. You need some scaffolding to be able to use constructors, though:

template <int I>
class X {
    template <typename T1>
    class YforIeq1 { /* meat of the class */ };
    template <typename T1, typename T2>
    class YforIeq2 { /* meat of the class */ };
public:
    template <typename T1, typename T2=boost::none_t/*e.g.*/>
    struct Y : boost::mpl::if_c<I==1,YforIeq1<T1>,YforIeq2<T1,T2> >::type {
        typedef typename mpl::if_c<I==1,YforIeq1<T1>,YforIeq2<T1,T2> >::type YBase;
        /* ctor forwarding: C++0x */
        using YBase::YBase;
        /* ctor forwarding: C++03 (runs into perfect fwd'ing problem)*/
        Y() : YBase() {}
        template <typename A1>
        Y(const A1&a1) : YBase(a1) {}
        template <typename A1, typename A2>
        Y(const A1&a1, const A2&a2) : YBase(a1,a2) {}
        // ...
    };
};

If there's a problem with both YforIeqN being instantiated for each X, then you can try wrapping them as a nullary meta function (something along the way mpl::apply does) and use mpl::eval_if_c.

Cicily answered 18/4, 2011 at 9:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.