SFINAE技术用于自由函数包装器

时间:2014-03-14 15:30:37

标签: c++ templates c++11 sfinae c++14

我真的希望能够拥有一个适应任何类型的自由函数。

e.g。

template <typename T> bool ReadLine(T & reader, std::string & line) 
{
     return reader.ReadString(line); 
}

对于某些T,正确的函数是reader.ReadString(buffer)。但对于其他人来说,它应该是reader.ReadLine(缓冲区)。当然,未来可能还有其他模式。关键是要使自由函数ReadLine(从,进入)适应任何合理的一组来自&amp; into(我强制目标缓冲区是一个std :: string来简化这里的事情)。

现在,我可以为我想要的任何具体类型创建一个非模板版本的ReadLine,但我真正需要的是能够对类型类进行部分专门化,例如那些支持模式阅读器的类.ReadString()最终都使用它,而那些支持reader.ReadLine()的人使用它,并且将来我可以添加其他模式而不会打扰任何已经有效的模式。

我知道我可以创建一个策略类,例如LineReaderPolicy,它知道给定的T使用哪种模式(根据T将其部分专用,以将其映射到正确的模式。

但是有更好的,更多的C ++ 14解决方法吗?

这是其中之一“上帝看起来模板真的非常接近真实,非常有用......但对于这种不断反复出现的问题...”

C ++ 11/14的兼容性比以往任何时候都好,但似乎这个基本问题仍未得到解决?或者是它?!

您如何建议我编写一组适应任何合理T的自由函数来读取它的一行? T是字符串流,输出迭代器,文件句柄,字符串,字符串视图等等......

我不能认为C ++真的成熟了,直到我能写出一个合理的

template <typename T> size_t length(T t) { return t.size(); }

然后我可以将其扩展到任何合理的T,并且不必编写知道T的许多细节的代码,但是可以通过这种灵活的自由函数适配器与吨Ts进行互操作......

3 个答案:

答案 0 :(得分:8)

如果您可以确保最多定义reader.ReadStringreader.ReadLine中的一个,请使用SFINAE来控制重载(Live at Coliru):

template <typename T>
auto ReadLine(T& reader, std::string& line) -> decltype(reader.ReadString(line)) {
  return reader.ReadString(line); 
}

template <typename T>
auto ReadLine(T& reader, std::string& line) -> decltype(reader.ReadLine(line)) {
  return reader.ReadLine(line); 
}

不适用的实现将触发SFINAE并从过载集中删除,只留下正确的实现。

在C ++ 14中,您可以省略尾随返回类型,只需使用返回类型演绎。

在将来的C ++版本中,Concepts Lite将使这一过程更加干净。鉴于歧视两种不同类型读者的概念 - 比如StringReaderLineReader

template <typename T>
concept bool StringReader() {
  return requires(T& reader, std::string& line) {
    {reader.ReadString(line)} -> bool;
  };
}

template <typename T>
concept bool LineReader() {
  return requires(T& reader, std::string& line) {
    {reader.ReadLine(line)} -> bool;
  };
}

您将能够直接将您的实现约束到为这些概念建模的类型集:

bool ReadLine(StringReader& reader, std::string& line) {
  return reader.ReadString(line); 
}

bool ReadLine(LineReader& reader, std::string& line) {
  return reader.ReadLine(line); 
}

希望您能够在其他地方重复使用这些概念来证明&#34; new-special-better&#34;语法比旧讨厌的语法长得多。 Concepts Lite还可以通过明确的消歧来处理模拟两个概念的类型:

template <typename T>
  requires StringReader<T>() && LineReader<T>()
bool ReadLine(T& reader, std::string& line) {
  // Call one, or the other, or do something completely different.
}

答案 1 :(得分:3)

  

我不能认为C ++真的成熟了,直到我能写出一个合理的

template <typename T> size_t length(T t) { return t.size(); }
     

然后我可以将其扩展到任何合理的T,并且不必编写知道T的许多细节的代码,但是可以通过这种灵活的自由函数适配器与吨Ts进行互操作......

你想要Concepts Lite,我希望它能在C ++ 17中出现:

template<typename T>
  concept bool Has_size()
  {
    return requires(T t) {
      t.size() -> Integral;
    };
  }

template<typename T>
  concept bool Has_length()
  {
    return requires(T t) {
      t.length() -> Integral;
    };
  }

template <Has_size T> auto length(T t) { return t.size(); }
template <Has_length T> auto length(T t) { return t.length(); }

在此之前,您可以使用SFINAE来模拟它,这可以通过多种方式完成,您的示例中最简洁的可能只是一个尾随返回类型,如Casey的回答所示。

template <typename T>
  auto length(T t) -> decltype(t.size()) { return t.size(); }
template <typename T>
  auto length(T t) -> decltype(t.length()) { return t.length(); }

答案 2 :(得分:1)

template<typename>struct type_sink{typedef void type;};
template<typename T>using TypeSink=typename type_sink<T>::type;
template<typename T,typename=void>
struct has_x:std::false_type{};
template<typename T>
struct has_x<T,TypeSink(decltype(std::declval<T>().x())>:std::true_type{};

是一个非常简单的traits类,用于&#39;类型是否有.x()方法,并且可以推广。

然后我们可以使用标签调度将我们的功能引导到自定义实现。

template<typename T>
bool do_x_helper(T&t, std::true_type ){
  return t.x();
}
template<typename T>
bool do_x_helper(T7, std::false_type ){
  // alternative
}
template<typename T>
bool do_x(T& t){
  return do_x_helper( t, has_x<T>() );
}

此技术可让您拥有复杂的测试和方法体。您基本上必须手动执行重载解析和调度,但它可以让您完全控制。它类似于std算法用于调度迭代器类型的技术:在这种情况下,调度不仅仅是true_typefalse_type