【问题标题】:Can I overload functions with type-traits?我可以重载具有类型特征的函数吗?
【发布时间】:2014-01-09 01:08:41
【问题描述】:

假设我有六种类型,它们都属于一个概念类别。
这是一个显示这一点的图表:


或者也许为您提供更具体的示例:


我想编写两个函数来处理所有 6 种类型。
“类别 1”中的类型以特定方式处理,而“类别 2”中的类型以不同方式处理。

让我们进入代码。 首先,我将创建六种类型。

//Category 1 Types
class Type_A{};
class Type_B{};
class Type_C{};

//Category 2 Types
class Type_D{};
class Type_E{};
class Type_F{};

接下来,我将创建两个类型特征,以便可以在编译时发现类型的类别。

/* Build The Category 1 Type Trait */

//Type_A Type Trait
template <typename T>
struct Is_Type_A {
  static const bool value = false;
};
template <>
struct Is_Type_A<Type_A> {
  static const bool value = true;
};

//Type_B Type Trait
template <typename T>
struct Is_Type_B {
  static const bool value = false;
};
template <>
struct Is_Type_B<Type_B> {
  static const bool value = true;
};

//Type_C Type Trait
template <typename T>
struct Is_Type_C {
  static const bool value = false;
};
template <>
struct Is_Type_C<Type_C> {
  static const bool value = true;
};

//Category 1 Type Trait
template <typename T>
struct Is_Type_From_Category_1 {
  static const bool value = Is_Type_A<T>::value || Is_Type_B<T>::value || Is_Type_C<T>::value;
};

/* Build The Category 2 Type Trait */

//Type_D Type Trait
template <typename T>
struct Is_Type_D {
  static const bool value = false;
};
template <>
struct Is_Type_D<Type_D> {
  static const bool value = true;
};

//Type_E Type Trait
template <typename T>
struct Is_Type_E {
  static const bool value = false;
};
template <>
struct Is_Type_E<Type_E> {
  static const bool value = true;
};

//Type_F Type Trait
template <typename T>
struct Is_Type_F {
  static const bool value = false;
};
template <>
struct Is_Type_F<Type_F> {
  static const bool value = true;
};

//Category 1 Type Trait
template <typename T>
struct Is_Type_From_Category_2 {
  static const bool value = Is_Type_D<T>::value || Is_Type_E<T>::value || Is_Type_F<T>::value;
};

现在我有两个类型特征来区分这六种类型中的每一种属于哪个类别,我想编写两个函数。一个函数将接受类别 1 中的所有内容,而另一个函数将接受类别 2 中的所有内容。有没有办法在不创建某种调度函数的情况下做到这一点?我能找到一种只有两个功能的方法吗?每个类别一个?


编辑:我曾尝试像这样使用 enable_if,但这样的尝试会导致编译器错误。

//Handle all types from Category 1
template<class T ,class = typename std::enable_if<Is_Type_From_Category_1<T>::value>::type >
void function(T t){
    //do category 1 stuff to the type
    return;
}

//Handle all types from Category 2
template<class T ,class = typename std::enable_if<Is_Type_From_Category_2<T>::value>::type >
void function(T t){
    //do category 2 stuff to the type
    return;
}

编辑 2: 我已经尝试了链接中提供的代码,但这并不是关于是否调用该函数的是或否决定。考虑到两种类型特征,我应该调用哪个函数。这将是一个重新定义错误。

//Handle all types from Category 2
template<class T, class dummy = typename std::enable_if< Is_Type_From_Category_1<T>::value, void>::type>
void function(T t){
    //do category 1 stuff to the type
    return;
}
//Handle all types from Category 2
template<class T, class dummy = typename std::enable_if< Is_Type_From_Category_2<T>::value, void>::type>
void function(T t){
    //do category 2 stuff to the type
    return;
}

【问题讨论】:

  • Understanding SFINAE的可能重复
  • @Potatoswatter 对,但我认为我不能用类型特征重载,可以吗?必须先发现它们是真是假;这意味着我必须派遣?
  • 查看链接的问答。 enable_if 不是您要找的吗?
  • @Potatoswatter 很好,我已经尝试过 enable_if,但我无法编写两个使用 enable_if 的相同函数签名;至少我不这么认为。我将尝试更新我的问题。
  • stackoverflow.com/questions/15427667/… 我认为您可以接受一个不错的简短答案

标签: c++ templates c++11 template-meta-programming typetraits


【解决方案1】:

不允许两个函数签名仅因模板参数的默认值而有所不同。如果你明确地调用function&lt; int, void &gt;会发生什么?

enable_if 的通常用法是作为函数返回类型。

//Handle all types from Category 1
template<class T >
typename std::enable_if<Is_Type_From_Category_1<T>::value>::type
function(T t){
    //do category 1 stuff to the type
    return;
}

//Handle all types from Category 2
template<class T >
typename std::enable_if<Is_Type_From_Category_2<T>::value>::type
function(T t){
    //do category 2 stuff to the type
    return;
}

【讨论】:

  • 如果返回类型不同,为什么两个函数看起来都返回void?
  • @TrevorHickey enable_if 有一个可选的第二个返回类型模板参数,默认为void
【解决方案2】:

我认为使用标签发送会比 SFINAE 更容易。

template<class T>
struct Category;

template<>
struct Category<Type_A> : std::integral_constant<int, 1> {};
template<>
struct Category<Type_B> : std::integral_constant<int, 1> {};
template<>
struct Category<Type_C> : std::integral_constant<int, 1> {};

template<>
struct Category<Type_D> : std::integral_constant<int, 2> {};
template<>
struct Category<Type_E> : std::integral_constant<int, 2> {};
template<>
struct Category<Type_F> : std::integral_constant<int, 2> {};

template<class T>
void foo(std::integral_constant<int, 1>, T x)
{
    // Category 1 types.
}

template<class T>
void foo(std::integral_constant<int, 2>, T x)
{
    // Category 2 types.
}

template<class T>
void foo(T x)
{
    foo(Category<T>(), x);
}

【讨论】:

  • template&lt;class T&gt; struct Category&lt;Type_A&gt; : std::integral_constant&lt;int, 1&gt; {}; 你想使用完全专业化吗?
  • 我认为通过将 integral_constants 替换为标签类型 Category1Category2 并删除当时多余的 cmets,可以使这更清晰和更具可读性。
【解决方案3】:

作为通过“特征”选择类别的替代方法,您还可以考虑 CRTP(其中类型以类别为基础):

template<class Derived> class category1 {};
template<class Derived> class category2 {};

class A1: public category1<A1> { ..... };
class A2: public category2<A2> { ..... };
class B1: public category1<B1> { ..... };
class B2: public category2<B2> { ..... };

template<class T>void funcion_on1(category1<T>& st)
{
   T& t = static_cast<T&>(st);
   .....
}

template<class T>void funcion_on1(category2<T>& st)
{
   T& t = static_cast<T&>(st);
   .....
}

优点是具有较少污染的命名空间。

【讨论】:

  • 这不起作用,因为对模板特化的引用不会绑定到基类子对象,也不会从中推断出模板参数。
  • 当然,& 可以是 const& 或 &&,具体取决于您要绑定的内容。我有很多图书馆都是这样工作的,所以-请-不要以偏见来判断!那里没有模板专业化。只是纯粹的类型推导。我真的没有在代码中看到你在说什么
【解决方案4】:

我从R. Martinho Fernandes 学到了以下技术。下面显示的代码是为了说明问题的基本原理而编写的,但您应该参考此blog post 以获取完整的技巧以使其美观。

您已经提到由于签名相同而遇到问题。诀窍是使类型不同。

您的第二种方法很接近,但我们不能使用void 作为std::enable_if&lt;&gt; 的结果类型。

请注意,以下代码无法编译,并且为 std::enable_if&lt;&gt; 指定 void 不会改变任何内容,因为无论如何默认都是 void

#include <iostream>

class A {};
class B {};

template <
    typename T, 
    typename = typename std::enable_if<std::is_same<T, A>::value>::type>
void F(T) {
  std::cout << "A" << std::endl;
}

template <
    typename T, 
    typename = typename std::enable_if<std::is_same<T, B>::value>::type>
void F(T) {
  std::cout << "B" << std::endl;
}

int main() {
  F(A{});
  F(B{});
}

正如您已经描述的那样,原因是签名是相同的。让我们区分它们。

#include <iostream>

class A {};
class B {};

template <
    typename T,
    typename std::enable_if<std::is_same<T, A>::value, int>::type = 0>
void F(T) {
  std::cout << "A" << std::endl;
}

template <
    typename T, 
    typename std::enable_if<std::is_same<T, B>::value, int>::type = 0>
void F(T) {
  std::cout << "B" << std::endl;
}

int main() {
  F(A{});
  F(B{});
}

打印:

A
B

我们现在区分了这两个函数的类型,因为第二个模板参数不是类型,而是int

这种方法比在返回类型中使用 std::enable_if&lt;&gt; 更可取,例如,因为构造函数没有返回类型,所以该模式不适用于那些。

注意:std::is_same&lt;&gt; 与单个类一起使用以简化条件。

【讨论】:

  • 假人int 可能会妨碍您,尤其是使用参数包时。为什么不遵循 SFINAE 的返回类型习语?
  • 答案中描述的首选此方法的要点是因为返回类型习语不能应用于构造函数。但是,您提出了一个很好的观点,即这种模式不适用于可变参数模板参数。也许这两种方法都可以考虑,或者有一种模式可以应用于所有情况。
  • 还有另一个地方可以粘贴 SFINAE,在异常规范中为 noexcept( SFINAE )。非推导函数参数也是一个钩子。但是,通常的用法是默认使用返回类型,就像从 C++98 开始那样。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-22
  • 2012-08-28
相关资源
最近更新 更多