专门基于方法的模板

Adr*_*ski 14 c++ generics templates 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在哪里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();
}
Run Code Online (Sandbox Code Playgroud)

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

Gui*_*cot 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();
}
Run Code Online (Sandbox Code Playgroud)

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

void_t 实施如下:

template<typename...>
using void_t = void;
Run Code Online (Sandbox Code Playgroud)

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

struct Bummer {
    void first() {}
    void second() {}
};
Run Code Online (Sandbox Code Playgroud)

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


如果你真的想测试一个类型是否扩展另一个(或者实现,在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();
}
Run Code Online (Sandbox Code Playgroud)

要阅读有关此主题的更多信息,请查看cpprefence上的sfinae,您可以检查标准库提供的可用特征.


Ric*_*ges 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;

}
Run Code Online (Sandbox Code Playgroud)