使用SFINAE重载C++泛型函数

3
我正在尝试编写一个简单的序列化协议。容器类型的send应该在每个元素上调用send(这些元素本身可能是容器类型)。
但我无法想出如何实现这一点。我已经编写了三个函数,它们各自都可以工作,但我无法想出如何让C++选择和分派正确的函数。我该怎么做?
目前,clang说我不能使用相同签名重新定义send(即使是为了SFINAE)。
我正在使用C++17,但我不能使用外部库,比如boost。[编辑]这是一个个人挑战。我想知道如何使用纯C++来实现这一点。毕竟,那个库必须由某人用纯C++编写,对吧?[/编辑]
#include <vector>
#include <utility>
#include <iostream>
#include <type_traits>

template <class pair_t>
std::void_t<typename pair_t::first_type, typename pair_t::second_type>
send(pair_t pair) {
    using first_t = typename pair_t::first_type;
    using second_t = typename pair_t::second_type;

    send<first_t>(std::get<0>(pair));
    send<second_t>(std::get<1>(pair));
}

template <typename list_t>
std::void_t<typename list_t::value_type, typename list_t::size_type>
send(list_t list) {
    using value_t = typename list_t::value_type;

    for (const value_t& elem : list) {
        send<value_t>(elem);
    }
}

template <typename int_t>
void
send(typename std::enable_if<std::is_integral<int_t>::value, int_t>::type val) {
    std::cout << "Sending integral val: " << val << std::endl;
}

int obj0 = 1;
std::vector<int> obj1;
std::pair<int, int> obj2;
std::vector<std::pair<int,int>> obj3;
int main() {
    // send<int>(obj0);
    // send(obj1);
    // send(obj2);
    // send(obj3);
}

为什么不能使用外部库?这是作业吗?还是面试任务?或者是有政治限制的工作项目? - Martin Bonner supports Monica
4
一个个人的挑战。我想知道如何使用纯C++实现这个功能。毕竟,那个库一定是由某个人编写的,对吧? - charmoniumQ
你应该将那个解释编辑到你的问题中。 - Martin Bonner supports Monica
3个回答

2
您的问题是,就编译器而言,您对于pair和list的声明都类似于以下内容:
template <typename T>
blah send(T arg) {
    ...
}

...而且在该函数签名中没有使用enable_if。我认为你需要将enable_if放入参数类型中。(我还会通过常量引用来使用list_t - 不要不必要地复制列表)。


我认为如果list_t::value_typelist_t::size_type不存在,扩展std::void_t<typename list_t::value_type, typename list_t::size_type>将失败。我该如何检查这些内容? - charmoniumQ

1
你可以使用模板特化:
template <typename T> void send(const T&);

namespace detail {
  template <typename T> struct Send;  // primary template

  template <typename T> struct Send<std::vector<T>> {  // specialization for vector
    static void send(const std::vector<T>& v) {
      for (const auto e : v) ::send(e);
    }
  };

  template <typename T, typename U> struct Send<std::pair<T, U>> {  // for pair
    static void send(const std::pair<T, U>& p) {
      ::send(p.first); ::send(p.second);
    }
  };

  template <> struct Send<int> {  // for int
    static void send(int i) { std::cout << i; }
  };
}

template <typename T> void send(const T& arg) { detail::Send<T>::send(arg); }

int main() {
  int i = 0;
  std::vector<int> vi = { 1, 2, 3 };
  std::pair<int, int> pi = { 4, 5 };
  std::vector<std::pair<int, int>> vpi = { { 7, 8 }, { 9, 0 } };
  send(i);
  send(vi);
  send(pi);
  send(vpi);
}

演示链接:https://wandbox.org/permlink/cQNpsgSFYmiurqRT

请注意,此解决方案需要为您想要支持的所有容器类型进行特殊化处理(这与“对任何具有嵌套value_typesize_type的类型使用此实现”不同)。


或者,您也可以选择使用SFINAE:

template <typename T> struct is_vector : std::false_type { };
template <typename T> struct is_vector<std::vector<T>> : std::true_type { };

template <typename T> struct is_pair : std::false_type { };
template <typename T, typename U> struct is_pair<std::pair<T, U>> : std::true_type { };

template <typename T> void send(T i, std::enable_if_t<std::is_integral_v<T>, int> = 0) {
  std::cout << i;
}

template <typename T> void send(const T& p, std::enable_if_t<is_pair<T>::value, int> = 0) {
  send(p.first); send(p.second);
}

template <typename T> void send(const T& v, std::enable_if_t<is_vector<T>::value, int> = 0) {
  for (const auto & e : v) send(e);
}

... // same main

或者,你也可以按照以下方式检查成员类型是否存在:

template <typename T> struct has_value_type {
  using yes = char[1]; using no = char[2];
  template <typename C> static constexpr yes& test(typename C::value_type*);
  template <typename> static constexpr no& test(...);
  static constexpr bool value = sizeof(test<T>(nullptr)) == sizeof(yes);
};   

... // similarly: has_size_type, has_first_type, and has_second_type

template <typename T> void send(T i, std::enable_if_t<std::is_integral_v<T>, int> = 0) {
  std::cout << i;
}

template <typename T> void send(const T& p, std::enable_if_t<has_first_type<T>::value && has_second_type<T>::value, int> = 0) {
  send(p.first); send(p.second);
}

template <typename T> void send(const T& v, std::enable_if_t<has_value_type<T>::value && has_size_type<T>::value, int> = 0) {
  for (const auto & e : v) send(e);
}

... // same main

演示链接:https://wandbox.org/permlink/qD7vp2ebzFaR15qf


最后一个解决方案对于一对向量不起作用。https://wandbox.org/permlink/bipJY3cdB4isLrZE - charmoniumQ
@charmoniumQ 并不会造成影响,这只是由于定义的顺序不同而已。如果你将声明和定义分开,那么它们的顺序就不会有影响了:https://wandbox.org/permlink/8LUGCwPwQAWvJulQ - Daniel Langr

1
到目前为止,最简单的解决方法是使用标签分派。首先编写属性的特征。
template<class T, class=void>
struct is_pair; // inherits from true_type or false_type depending on if T is a pair.
template<class T, class=void>
struct is_list;

现在我们分派。
namespace impl {
  template <class pair_t>
  void send(pair_t pair, std::true_type /* is pair */, std::false_type, std::false_type) {
    using first_t = typename pair_t::first_type;
    using second_t = typename pair_t::second_type;

    send<first_t>(std::get<0>(pair));
    send<second_t>(std::get<1>(pair));
  }

  template <typename list_t>
  void send(list_t list, std::false_type, std::true_type /* list */, std::false_type) {
    using value_t = typename list_t::value_type;

    for (const value_t& elem : list) {
      send<value_t>(elem);
    }
  }

  template <typename int_t>
  void send(int_t, std::false_type, std::false_type, std::true_type /* is integral */) {
    std::cout << "Sending integral val: " << val << std::endl;
  }
}
template<class T>
auto send( T t )
-> decltype( impl::send( std::move(t), is_pair<T>{}, is_list<T>{}, std::is_integral<T>{} ) )
{ impl::send( std::move(t), is_pair<T>{}, is_list<T>{}, std::is_integral<T>{} ); }

我无法让这个工作起来这里是我的尝试。 - charmoniumQ
@charmoniumQ 前向声明了 send2 模板,然后实现了 send,接着实现了 send2。然后加上 (void)pi; 因为你忘记使用它了。 ;) - Yakk - Adam Nevraumont

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接