10

How can it be checked that some type is explicitly (or vice versa implicitly) constructible from other type? Is it any SFINAE trick in this situation?

I can write is_explicitly_constructible as a combination of std::is_constructible and std::is_convertible:

#include <type_traits>

template <typename Type, typename Argument>
struct is_explicitly_constructible
    : std::bool_constant
        <
            std::is_constructible<Type, Argument>::value &&
            !std::is_convertible<Argument, Type>::value
        >
{
};

But do I take into account all possible cases in such code?

Constructor
  • 7,273
  • 2
  • 24
  • 66
  • It seems to me that combining `declval` with `static_cast` should determine whether one type is convertible from another type. Combine that with a typical SFINAE test, and that should do the trick. – Sam Varshavchik Mar 14 '17 at 13:03
  • Is the question just: is this correct? If so, yes it is. – Barry Mar 14 '17 at 13:29
  • @SamVarshavchik But I want to check if one type _constructible_ from another type, not _convertible_. And `static_cast` supports convertion of types that are not classes themselves. – Constructor Mar 14 '17 at 13:32
  • @Barry And no pitfalls here? Like competing constructors or something else... – Constructor Mar 14 '17 at 13:34
  • @Constructor `std::is_constructible` does the right thing. It doesn't check if literally the constructor `X(Y )` exists - just checks if a hypothetical `X x(y);` would be well-formed. – Barry Mar 14 '17 at 13:35

1 Answers1

13

Yes, this is correct. A type T is explicitly constructible from an argument A if

  1. It is constructible at all from A. That is, a hypothetical T x(a) is valid.
  2. Implicit conversions are ill-formed. That is, the hypothetical function T test() { return a; } would be ill-formed.

std::is_constructible tests #1, and std::is_convertible tests the validity of #2. Hence, wanting #1 and not #2 would be is_explicitly_constructible just as #1 and #2 would be is_implicitly_constructible.


Such an is_explicitly_constructible/is_implicitly_constructible pair is how you would implement a constructor being conditionally explicit. For instance, in libstdc++, these two constructors for optional exist:

implicit:

 template <typename _Up = _Tp,
            enable_if_t<__and_<
              __not_<is_same<optional<_Tp>, decay_t<_Up>>>,
              is_constructible<_Tp, _Up&&>,   // (*) 
              is_convertible<_Up&&, _Tp>      // (*)
              >::value, bool> = true>
  constexpr optional(_Up&& __t)

explicit:

  template <typename _Up = _Tp,
            enable_if_t<__and_<
              __not_<is_same<optional<_Tp>, decay_t<_Up>>>,
              is_constructible<_Tp, _Up&&>,        // (*)
              __not_<is_convertible<_Up&&, _Tp>>   // (*)
              >::value, bool> = false>
  explicit constexpr optional(_Up&& __t);

You can see that libstdc++ uses the same expressions that you do.

Barry
  • 286,269
  • 29
  • 621
  • 977