Enable template only if the return expression is valid

戏子无情 提交于 2019-12-24 21:23:00

问题


I want to write a wrapper over std::ostream in this style:

#include <iostream>

struct OstreamWrapper {
  OstreamWrapper(std::ostream &out) : out(out) {}

  template< typename T >
  decltype(auto) operator<<(T &&arg) {
    return out << std::move< T >(arg);
  }

  std::ostream &out;
};

int main() {
  OstreamWrapper wrap(std::cout);
  wrap << "Hello, world!";  // This works
  wrap << std::endl;        // This does not work
  return 0;
}

The problem with this approach is that it does not work (for example) with std::endl, because (as I get it) std::endl is overloaded, and the compiler does not know how to resolve the overload when it evaluates the template.

I believe that this situation can be fixed with some clever SFINAE, but I cannot find something that works. I think I need something like "enable this template only when cout << arg is a well formed expression", but I do not know how to express that.

For example, I tried this:

  template< typename T,
            typename = decltype(out << arg) >
  decltype(auto) operator<<(T &&arg) {
    return out << std::move< T >(arg);
  }

But this is not ok, because then the template expressions are evaluated, arg is not yet defined.

  template< typename T,
            typename = decltype(out << std::declval< T >()) >
  decltype(auto) operator<<(T &&arg) {
    return out << std::move< T >(arg);
  }

This compiles, but does not do what I want, because it requires the type T to be known, while my problem actually lies in establishing how to overload its parameter.

I have also tried more obscure conditions based on std::enable_if and std::is_invocable and std::result_of, but they introduced a lot of errors I could not understand and it probably would be pointless to summary here all the attempts.

Is there a way to do this thing properly? Possibly with C++14, so the codebase remains more backward compatbile, but if C++17 is necessary it is ok as well.


回答1:


You might add overload to force type (and so unique available overload is chosen):

struct OstreamWrapper {
    explicit OstreamWrapper(std::ostream &out) : out(out) {}

    template< typename T >
    decltype(auto) operator<<(T &&arg) {
        return out << std::forward<T>(arg);
    }

    decltype(auto) operator<<(std::ostream& (&arg)(std::ostream&)) {
        return out << arg;
    }

    std::ostream &out;
};

Demo




回答2:


std::endl is not overloaded. It's a function template that's declared like this:

template< class CharT, class Traits >
std::basic_ostream<CharT, Traits>& endl( std::basic_ostream<CharT, Traits>& os );

The reason it works directly for std::ostream is that the appropriate operator << (the one for stream manipulators) is a regular member function (though generated from the basic_ostream template for char). It expects a concrete manipulator type. Template argument deduction can be used with this parameter type to deduce the arguments of the correct endl specialization.

Since you seem to support only std::ostream, the solution in @Jarod42's answer is the way to go.



来源:https://stackoverflow.com/questions/51170934/enable-template-only-if-the-return-expression-is-valid

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!