基于方法的专业模板

时间:2016-08-28 17:33:22

标签: c++ templates generics duck-typing

最近我用Java编写了很多东西,现在我回到了我的C ++根源(我真的开始错过了指针和分段错误)。知道C ++对模板的广泛支持我想知道它是否具有Java的一些功能,这些功能对编写通用代码很有用。让我们说我有两组课程。其中一个使用first()方法,另一个使用second()方法。有没有一种方法可以根据一个类拥有的方法专门化编译器选择的模板?我瞄准的行为类似于Java:

public class Main {
    public static void main(String[] args) {
        First first = () -> System.out.println("first");
        Second second = () -> System.out.println("second");
        method(first);
        method(second);
    }

    static <T extends First> void method(T argument) {
        argument.first();   
    }

    static <T extends Second> void method(T argument) {
        argument.second();
    }
}

FirstSecond是接口。我知道我可以通过从上层阶级中推导出这两个群体来对这两个群体进行分组,但并不总是可行(C ++中没有自动装箱,有些类别不会从共同的祖先继承)。

我需要的一个很好的例子是STL库,其中一些类具有push()等方法,而另一些类具有insert()push_back()。假设我想创建一个函数,它必须使用可变参数函数将多个值插入到容器中。在Java中,它易于执行,因为集合具有共同的祖先。另一方面,在C ++中并非总是如此。我通过duck-typing尝试了它,但是编译器产生了一条错误消息:

template <typename T>
void generic_fcn(T argument) {
    argument.first();
}

template <typename T>
void generic_fcn(T argument) {
    argument.second();
}

所以我的问题是:如果不通过专门化每一个案例来创建不必要的boileplate代码,是否可以实现这样的行为?

4 个答案:

答案 0 :(得分:16)

而不是<T extends First>,你将使用我们称之为sfinae的东西。这是一种基于参数类型在函数上添加constaints的技术。

以下是您在c ++中的表现方式:

template <typename T>
auto generic_fcn(T argument) -> void_t<decltype(argument.first())> {
    argument.first();
}

template <typename T>
auto generic_fcn(T argument) -> void_t<decltype(argument.second())> {
    argument.second();
}

对于存在的函数,编译器需要argument.second()的类型或argument.first()的类型。如果表达式没有产生类型(即。T没有first()函数),编译器将尝试另一个重载。

void_t的实施方式如下:

template<typename...>
using void_t = void;

另一件好事是,如果你有这样的课程:

struct Bummer {
    void first() {}
    void second() {}
};

然后编译器会有效地告诉你调用是不明确的,因为类型匹配两个约束。

如果你真的想测试一个类型是否扩展了另一个(或者在c ++中实现它是同一个东西)你可以使用类型特征std::is_base_of

template <typename T>
auto generic_fcn(T argument) -> std::enable_if_t<std::is_base_of<First, T>::value> {
    argument.first();
}

template <typename T>
auto generic_fcn(T argument) -> std::enable_if_t<std::is_base_of<Second, T>::value> {
    argument.second();
}

要阅读有关此主题的更多信息,请在cpprefence上查看sfinae,然后检查标准库提供的available traits

答案 1 :(得分:5)

c ++中提供了很多选项。

我的偏好是赞成自由函数并正确返回任何结果类型。

#include <utility>
#include <type_traits>
#include <iostream>

struct X
{
  int first() { return 1; }
};

struct Y
{
  double second() { return 2.2; }
};


//
// option 1 - specific overloads
//

decltype(auto) generic_function(X& x) { return x.first(); }
decltype(auto) generic_function(Y& y) { return y.second(); }

//
// option 2 - enable_if
//

namespace detail {
  template<class T> struct has_member_first
  {
    template<class U> static auto test(U*p) -> decltype(p->first(), void(), std::true_type());
    static auto test(...) -> decltype(std::false_type());
    using type = decltype(test(static_cast<T*>(nullptr)));
  };
}
template<class T> using has_member_first = typename detail::has_member_first<T>::type;

namespace detail {
  template<class T> struct has_member_second
  {
    template<class U> static auto test(U*p) -> decltype(p->second(), void(), std::true_type());
    static auto test(...) -> decltype(std::false_type());
    using type = decltype(test(static_cast<T*>(nullptr)));
  };
}
template<class T> using has_member_second = typename detail::has_member_second<T>::type;

template<class T, std::enable_if_t<has_member_first<T>::value>* =nullptr> 
decltype(auto) generic_func2(T& t)
{
  return t.first();
}

template<class T, std::enable_if_t<has_member_second<T>::value>* =nullptr> 
decltype(auto) generic_func2(T& t)
{
  return t.second();
}

//
// option 3 - SFNAE with simple decltype
//

template<class T>
auto generic_func3(T&t) -> decltype(t.first())
{
  return t.first();
}

template<class T>
auto generic_func3(T&t) -> decltype(t.second())
{
  return t.second();
}


int main()
{
  X x;
  Y y;

  std::cout << generic_function(x) << std::endl;
  std::cout << generic_function(y) << std::endl;

  std::cout << generic_func2(x) << std::endl;
  std::cout << generic_func2(y) << std::endl;

  std::cout << generic_func3(x) << std::endl;
  std::cout << generic_func3(y) << std::endl;

}

答案 2 :(得分:4)

您可以按照以下方式发出呼叫:

#include<utility>
#include<iostream>

struct S {
    template<typename T>
    auto func(int) -> decltype(std::declval<T>().first(), void())
    { std::cout << "first" << std::endl; }

    template<typename T>
    auto func(char) -> decltype(std::declval<T>().second(), void())
    { std::cout << "second" << std::endl; }

    template<typename T>
    auto func() { return func<T>(0); }
};

struct First {
    void first() {}
};

struct Second {
    void second() {}
};

int main() {
    S s;
    s.func<First>();
    s.func<Second>();
}

如果某个类同时拥有这两个方法,则first优先于second 否则,func使用函数重载来测试这两种方法并选择正确的方法 这种技术称为 sfinae ,使用此名称在网上搜索以获取更多详细信息。

答案 3 :(得分:3)

这是一个小型库,可帮助您确定成员是否存在。

namespace details {
  template<template<class...>class Z, class always_void, class...>
  struct can_apply:std::false_type{};
  template<template<class...>class Z, class...Ts>
  struct can_apply<Z, std::void_t<Z<Ts...>>, Ts...>:std::true_type{};
}
template<template<class...>class Z, class...Ts>
using can_apply=details::can_apply<Z, void, Ts...>;

现在我们可以先编写并轻松编写第二个:

template<class T>
using first_result = decltype(std::declval<T>().first());
template<class T>
using has_first = can_apply<first_result, T>;

,同样适用于second

现在我们有了方法。我们想先拨打第一个或第二个。

template<class T>
void method_second( T& t, std::true_type has_second ) {
  t.second();
}
template<class T>
void method_first( T& t, std::false_type has_first ) = delete; // error message
template<class T>
void method_first( T& t, std::true_type has_first ) {
  t.first();
}
template<class T>
void method_first( T& t, std::false_type has_first ) {
  method_second( t, has_second<T&>{} );
}
template<class T>
void method( T& t ) {
  method_first( t, has_first<T&>{} );
}

这称为标签调度。

method调用method_first,确定T&是否可以.first()调用.first().。如果可以,则调用调用method_second

的调用

如果不能,则调用转发到.second()的那个并测试它是否=delete

如果两者都没有,则调用template<class T> void method(T & t) { if constexpr (has_first<T&>{}) { t.first(); } if constexpr (has_second<T&>{}) { t.second(); } } 函数,该函数在编译时生成错误消息。

有许多很多方法可以做到这一点。我个人喜欢标签调度,因为你可以获得比SFIANE生成的更好的错误消息。

在C ++ 17中,你可以更直接:

MyEnableQuery
相关问题