Вопрос

I would really like to be able to have a free function that adapts to whatever types its being given.

e.g.

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

For some T, the correct function is reader.ReadString(buffer). But for others, it should be reader.ReadLine(buffer). And of course, there may be other patterns in the future. The point being to adapt the free function ReadLine(from, into) to work with any reasonable set of from & into (I've forced the destination buffer to be a std::string to simplify things here).

Now, I can make a non-template version of ReadLine for any concrete type I want, but what I really need is the ability to partially specialize for classes of types, such that those which support the pattern reader.ReadString() all end up using that, and those which support reader.ReadLine() use that, and in the future I can add other patterns without disturbing anything that already works.

I'm aware that I can create a policy class, such as LineReaderPolicy, which knows for a given T which pattern to use (it would have to be partially specialized depending on T to map it to the correct pattern.

But is there a better, more C++14 way of solving this?

This is one of those "God it seems that templates are really, really close to being really, really useful... but for this constantly recurring problem..."

Compostability is better than ever with C++11/14, but it still seems that this fundamental problem is unsolved? Or is it?!

How would you suggest I write a set of free functions that adapt to any reasonable T to read a line from it? Whether T be a string stream, an output iterator, a file handle, a string, a string-view, etc., etc...

I just cannot think that C++ is really come of age until I can write a reasonable

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

For which I can then extend that into any reasonable T, and stop having to write code that know so many details of T, but can inter-operate with tons of Ts via such flexible free function adapters...

Это было полезно?

Решение

If you can ensure that at most one of reader.ReadString or reader.ReadLine is defined, use SFINAE to control overloading (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); 
}

The inapplicable implementation will trigger SFINAE and be removed from the overload set, leaving only the correct implementation.

In C++14 you'll be able to omit the trailing return type and simply use return type deduction.

In a future revision of C++, Concepts Lite will enable this to be done more cleanly. Given concepts that discriminate the two different kinds of readers - say StringReader and LineReader:

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;
  };
}

you'll be able to directly constrain your implementations to the set of types that model those concepts:

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

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

Hopefully you'll be able to reuse those concepts elsewhere to justify the "new-special-better" syntax being substantially longer than the old nasty syntax. Concepts Lite also will make it possible to handle types that model both concepts by explicit disambiguation:

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.
}

Другие советы

I just cannot think that C++ is really come of age until I can write a reasonable

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

For which I can then extend that into any reasonable T, and stop having to write code that know so many details of T, but can inter-operate with tons of Ts via such flexible free function adapters...

You want Concepts Lite, which I hope will arrive in 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(); }

Until then you can use SFINAE to emulate it, which can be done in many ways, the tersest for your example is probably just a trailing-return-type as shown in Casey's answer.

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(); }
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{};

is a pretty simple traits class for 'does a type have a .x() method, and can be generalized.

We can then use tag dispatching to direct our function to custom implementations.

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>() );
}

This technique lets you have complex tests and method bodies. You basically have to do overload resolution and dispatching manually, but it gives you full control. It is similar to the techniques that std algorithms use to dispatch iterator types: in that case more than just true_type and false_type is dispatched on.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top