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

孤者浪人 提交于 2019-12-28 11:10:53

问题


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. :/


回答1:


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;



回答2:


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




回答3:


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.




回答4:


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




回答5:


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.



来源:https://stackoverflow.com/questions/5659064/conditional-compile-time-inclusion-exclusion-of-code-based-on-template-argument

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!