Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

enum and static const member variable usage in template trait class

I want to test whether a class is streamable to ostream& by seeing whether an overload for operator<< is provided. Based on these posts, I tried to write another version using C++11. This is my attempt:

#include <iostream>
#include <type_traits>

namespace TEST{
  class NotDefined{};

  template<typename T> 
  NotDefined& operator << (::std::ostream&, const T&);

  template <typename T>
  struct StreamInsertionExists {
    static std::ostream &s;
    static T const &t;
    enum { value = std::is_same<decltype(s << t), NotDefined>() };
  };
}

struct A{
  int val;
    friend ::std::ostream& operator<<(::std::ostream&, const A&);
};

::std::ostream& operator<<(::std::ostream& os, const A& a)
{
  os << a.val;
  return os;
}

struct B{};

int main() {
  std::cout << TEST::StreamInsertionExists<A>::value << std::endl;
  std::cout << TEST::StreamInsertionExists<B>::value << std::endl;

}

But this fails to compile:

test_oper.cpp:40:57: error: reference to overloaded function could not be resolved; did you mean to call it?  
  std::cout << TEST::StreamInsertionExists<A>::value << std::endl;  

/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1/ostream:1020:1: note:  
  possible target for call  
    endl(basic_ostream<_CharT, _Traits>& __os)  

test_oper.cpp:30:17: note: candidate function not viable: no known conversion from 'TEST::NotDefined' to '::std::ostream &'  
  (aka 'basic_ostream<char> &') for 1st argument  
::std::ostream& operator<<(::std::ostream& os, const A& a)  

test_oper.cpp:15:15: note: candidate template ignored: couldn't infer template argument 'T'  
  NotDefined& operator << (::std::ostream&, const T&);  

However, if I replace the line
enum { value = std::is_same<decltype(s << t), NotDefined>() };
with
static const bool value = std::is_same<decltype(s << t), NotDefined>();
then everything compiles.

Why is there such a difference between the enum and the bool?

like image 737
Hsu Hau Avatar asked Sep 27 '22 22:09

Hsu Hau


1 Answers

value is an enum of anonymous name in StreamInsertionExists<T>. When you try to do:

std::cout << StreamInsertionExists<T>::value;

The compiler is doing overload lookup on operator<<(std::ostream&, StreamInsertionExists<T>::E). In the typical case, it'd do integral promotion on the enum and stream it as int. However, you additionally defined this operator:

template<typename T> 
NotDefined& operator << (std::ostream&, const T&);

That is a better match for the enum than the int version (Exact Match vs integral promotion), so it is preferred. Yes, it's a function template, but non-templates are only preferred if the conversion sequences match - and in this case they don't.

Thus, this line:

std::cout << TEST::StreamInsertionExists<A>::value << std::endl;

which is:

operator<<(operator<<(std::cout, TEST::StreamInsertionExists<A>::value), std::endl);

And the inner most operator<< call will use your NotDefined& template. Thus, the next step would be to find an appropriate function call for:

operator<<(NotDefined&, std::endl);

and there is no such overload for operator<< hence the error.

If you change value to be bool, there is no such problem because there is an operator<< that takes bool exactly: #6. That said, even with bool, your trait is still incorrect as it always returns false. Your NotDefined version actually returns a reference, so you'd have to check against that. And also, NotDefined& means not defined, so you'd have to flip the sign:

static const bool value = !std::is_same<decltype(s << t), NotDefined&>();

However, this is particularly error prone. Now cout << B{}; instead of failing to compile would instead give you a linker error, and cout << B{} << endl; gives you the same confusing overload error involving endl instead of simply statying that you can't stream a B.

You should prefer to just do:

template <typename...>
using void_t = void;

template <typename T, typename = void>
struct stream_insertion_exists : std::false_type { };

template <typename T>
struct stream_insertion_exists<T, void_t<
    decltype(std::declval<std::ostream&>() << std::declval<T>())
> > : std::true_type { };
like image 91
Barry Avatar answered Oct 05 '22 23:10

Barry