独立于模板参数位置的专用模板分派

Specialized template dispatch independent of template parameter position

我遇到了一个问题,即需要一个模板函数,只要它的模板参数相同且与位置无关,它就会具有相同的输出。鉴于总是有两个参数。

我有一个功能:

template<typename Lhs, typename Rhs>
int func();

我希望func<int, float>()func<float, int>()调用相同的代码。

我想到了一个我想避免的宏,但是当两种类型相同时我需要不重复代码。所以一个宏如:

#define DEF_TEMPL_IMPL(lhs, rhs, ret) \
template<>\
auto func<lhs, rhs>(){ return ret; }\
template<>\
auto func<rhs, lhs>(){ return func<lhs, rhs>(); }

将无法编译,因为 DEF_TEMPL_IMPL(float, float, 3) 会导致重新定义 func<>

我认为 SFINAE 是这里的答案,但想不出一个解决方案。

我会继续思考这个问题,但在回答这个问题之前,一些堆栈溢出方面的伟人可能有比我想出的更好或更优雅的解决方案。

那么如何实现呢?

您可以只为每个类型对编写一个专业化,然后将您的主要模板委托给 func<Rhs,Lhs>()(如果它被调用):

//This will get called if no specializations match
template<typename Lhs, typename Rhs>
int func() {
    //Check if there is a specialization for the flipped pair
    return func<Rhs, Lhs>();   
}

//This will get called for <int,float> and <float,int>
template <>
int func<int,float>() {
    return 42;   
}

//Ditto for <bool,float> and <float,bool>
template <>
int func<bool,float>() {
    return 0xbeef;   
}

//Specializations with the same arguments are also supported by this scheme
template <>
int func<bool,bool>() {
    return 12;   
}

Live Demo

您可以定义类型的顺序(即 int - 1、float - 2、char - 3)。 然后实现一个对这两种类型进行排序的函数:

#include <iostream>

template<typename T>
struct Order;

// There should be a better solution for getting type id in compile time
// This will let you specify the order
template<>
struct Order<int>
{
    enum { Value = 1 };
};

template<>
struct Order<float>
{
    enum { Value = 2 };
};

template<>
struct Order<char>
{
    enum { Value = 3 };
};

template<typename A, typename B, bool swap>
struct Sort;

template<typename A, typename B>
struct Sort<A, B, false>
{
    typedef A First;
    typedef B Second;
};

template<typename A, typename B>
struct Sort<A, B, true>
{
    typedef B First;
    typedef A Second;
};


template<typename Lhs, typename Rhs>
int func_sorted()
{
    return 1;
}

template<>
int func_sorted<int, float>()
{
    return 2;
}

template<>
int func_sorted<float, int>()
{
    return 3;
}


template<typename Lhs, typename Rhs>
int func()
{
    typedef typename Sort<
        Lhs,
        Rhs,
        ((int)Order<Lhs>::Value > (int)Order<Rhs>::Value)>::First First;
    typedef typename Sort<
        Lhs,
        Rhs,
        ((int)Order<Lhs>::Value > (int)Order<Rhs>::Value)>::Second Second;
    return func_sorted<First, Second>();
}

所以下面的代码

int main()
{
    std::cout << func<int, float>() << std::endl;
    std::cout << func<float, int>() << std::endl;
}

将打印:

2
2

这是另一种可能的解决方案

template<typename A, typename B>
struct List { };

template<typename A, typename B>
struct Set { 
   Set(List<A, B>) { }
   Set(List<B, A>) { }
};

template<typename A>
struct Set<A, A> { 
   Set(List<A, A>) { }
};

现在,将每个写成 Set<A, B> 的重载,像这样

template<typename Lhs, typename Rhs>
int func() {
    return func(List<Lhs, Rhs>());
}

int func(Set<int, float>) {
    return 42;   
}

int func(Set<bool, float>) {
    return 0xbeef;   
}

int func(Set<bool, bool>) {
    return 12;   
}

这样,您还可以定义 func(List<float, int>),如果出于某种原因,float, int 的顺序很重要。如果您不小心同时定义了 Set<int,float>Set<float, int>,那么这个调用就会有歧义。

您还可以在定义重载时验证您没有重复的 Set<A, B> vs Set<B, A> 通过更改二进制 Set 的定义来定义对此

template<typename A, typename B>
struct Set { 
   Set(List<A, B>) { }
   Set(List<B, A>) { }

   friend void dupeCheck(List<A, B>) { }
   friend void dupeCheck(List<B, A>) { }
};

然而,这对于这个技巧来说并不是绝对必要的。特别是因为您的 JIT 编译器中的类型可以很容易地以一致的方式手动排序。