2

I have a type that derives from boost::variant<T, E>. I do the following, and I cannot use the copy constructor, I cannot figure out why, some SFINAE seems to fail. Looks that the boost::variant<T, E> construction is swallowing a T as ExpectedResult<T, E> in the inherited constructor. How could I fix it to make it work and with the simplest solution?

template <class T, class E>
class ExpectedResult : boost::variant<T, E> {
public:
    using boost::variant<T, E>::variant;
};

ExpectedResult<int,float> er;
ExpectedResult<int, float> er2 = er;


error: no type named 'type' in 'boost::enable_if<boost::mpl::and_<boost::mpl::not_<boost::is_same<Emotiv::Cortex::Utilities::ExpectedResult<int, float>, boost::variant<int, float> > >, boost::detail::variant::is_variant_constructible_from<const Emotiv::Cortex::Utilities::ExpectedResult<int, float> &, boost::mpl::l_item<mpl_::long_<2>, int, boost::mpl::l_item<mpl_::long_<1>, float, boost::mpl::l_end> > >, mpl_::bool_<true>, mpl_::bool_<true>, mpl_::bool_<true> >, void>'; 'enable_if' cannot be used to disable this declaration
        typename boost::enable_if<mpl::and_<
                                  ^~~~~~~~~~
note: in instantiation of member function 'boost::variant<int, float>::variant' requested here
    using boost::variant<T, E>::variant;
    while substituting deduced template arguments into function template 'ExpectedResult' [with T = Emotiv::Cortex::Utilities::ExpectedResult<int, float>]
    ExpectedResult<int, float> er2 = er;
Germán Diago
  • 7,473
  • 1
  • 36
  • 59

1 Answers1

4

Boost variant has a perfect forwarding constructor.

You are importing it into your class.

It guards against consuming self& by checking if self is exactly a boost::variant. You are passing it an ExpectedResult&.

This confuses it.

template <class T, class E>
struct ExpectedResult : boost::variant<T, E> {
  using boost::variant<T, E>::variant;
  ExpectedResult()=default;
  ExpectedResult(ExpectedResult const&)=default;
  ExpectedResult(ExpectedResult &&)=default;
  ExpectedResult(ExpectedResult & o):
    ExpectedResult( const_cast<ExpectedResult const&>(o) )
  {}
  ExpectedResult(ExpectedResult &&)=default;
  ExpectedResult(ExpectedResult const&& o):
    ExpectedResult( o ) // calls const& ctor
  {}
  ExpectedResult& operator=(ExpectedResult const&)=default;
  ExpectedResult& operator=(ExpectedResult &&)=default;
  ExpectedResult& operator=(ExpectedResult &) {
    return *this=const_cast<ExpectedResult const&>(o);
  }
  ExpectedResult& operator=(ExpectedResult const&& o){
    return *this = o; // const& assign
  }
};

I suspect the above defaulted and manually written special member functions may help.

To be complete, you'd also have to include volatile, exploding it by another bunch.

I would be careful about adding my own perfect forwarding constructor as well as a using parent constructor, as the rules for how these change change in C++17 in a possibly breaking way. I am currently very leery of using inherited constructors due to the breaking changes in C++17 I do not fully understand as yet.

Another approach would be to avoid inheriting construction and instead forward to the variant.

template <class T, class E>
struct ExpectedResult : boost::variant<T, E> {
  using base=boost::variant<T, E>;
  ExpectedResult()=default;
  ExpectedResult(ExpectedResult const&)=default;
  ExpectedResult(ExpectedResult &&)=default;
  ExpectedResult(ExpectedResult &&)=default;
  ExpectedResult& operator=(ExpectedResult const&)=default;
  ExpectedResult& operator=(ExpectedResult &&)=default;

  template<class T0, class...Ts,
    class=std::enable_if_t<
      std::is_constructible<base, T0, Ts...>::value
      && (
        (sizeof...(ts)!=0)
        || !std::is_same<std::decay_t<T0>, ExpectedResult>::value
      )          
    >
  >
  ExpectedResult(T0&&t0, Ts&&...ts):
    base(std::forward<T0>(t0), std::forward<Ts>(ts)...)
  {}
};

which is perfect forwarding with all of its imperfections, but close enough for most use.

An initializer_list<T>, Ts&&... constructor may make the perfect forwarding more perfect, so ExpectedResult<std::vector<int>, bool> er{ {1,2,3,4} } works.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Should the guard behavior be considered a bug? I find excessive all the necessary replication. Maybe I should submit a bug. – Germán Diago Feb 24 '17 at 16:42
  • 2
    @GermánDiago The type is probably not designed to be inherited from; but they could change some is_same to is_base_of to possibly make it work in your case. I don't know if that could cause other problems... – Yakk - Adam Nevraumont Feb 24 '17 at 17:03