33

Update: conditional explicit has made it into the C++20 draft. more on cppreference

The cppreference std::tuple constructor page has a bunch of C++17 notes saying things like:

This constructor is explicit if and only if std::is_convertible<const Ti&, Ti>::value is false for at least one i

How can one write a constructor that is conditionally explicit? The first possibility that came to mind was explicit(true) but that's not legal syntax.

An attempt with enable_if was unsuccessful:

// constructor is explicit if T is not integral
struct S {
  template <typename T,
            typename = typename std::enable_if<std::is_integral<T>::value>::type>
  S(T) {}

  template <typename T,
            typename = typename std::enable_if<!std::is_integral<T>::value>::type>
  explicit S(T) {}
};

with the error:

error: ‘template<class T, class> S::S(T)’ cannot be overloaded
explicit S(T t) {}
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
  • The [proposal that added](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4387.html) that to the draft standard has some examples. – Shafik Yaghmour Oct 07 '15 at 17:37
  • 6
    In your example both constructors are the same because default arguments are not apart of the signature. Perhaps something like http://coliru.stacked-crooked.com/a/6db9921c59138c60 – David G Oct 07 '15 at 17:37
  • 1
    My god it's just.. horrible! No C++17 for me, that's settled. – Lightness Races in Orbit Oct 07 '15 at 17:39
  • it's funny that the comittee discusses on "crucial" features like if teh tuple constructor should be explicit or not instead of discussing really usefull things we're laking like http (which they have decided that it is out of scope for C++0z). thanks comitee! – David Haim Oct 07 '15 at 17:54
  • 3
    See libstdc++'s [implementation](https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/std/tuple#L550-L570), it's exactly what Shafik posted. @LightnessRacesinOrbit IMO, this is a nice fix because it lets you write `return {x ,y, z};` in a function returning a `tuple` assuming all the types involved are implicitly convertible. – Praetorian Oct 07 '15 at 17:59
  • @Praetorian when I first saw that you could return like that I thought they'd accepted the paper that allowed returned init lists to use explicit constructors, then I was sad – Ryan Haining Oct 07 '15 at 18:04
  • Really they should just add a new language feature that lets you define `template struct tuple { Ts... v; };` and just call it a day. – Barry Oct 07 '15 at 18:46
  • @Praetorian: It may lead to useful outcomes but its implementation, and the hackery that inevitably ensues, is just gruesome. That's not the feature's fault: it's the patchwork language's fault. Surely it's time to put this hipster nightmare out of its misery? – Lightness Races in Orbit Oct 07 '15 at 23:25
  • 2
    @David: Successfully convincing people who want to think about X that they shouldn't until Y is settled often just means neither X nor Y happens. :P –  Oct 08 '15 at 00:56

2 Answers2

16

The proposal that added that N4387: Improving pair and tuple, revision 3 has an example of how it works:

Consider the following class template A that is intended to be used as a wrapper for some other type T:

#include <type_traits>
#include <utility>

template<class T>
struct A {
  template<class U,
    typename std::enable_if<
      std::is_constructible<T, U>::value &&
      std::is_convertible<U, T>::value
    , bool>::type = false
  >
  A(U&& u) : t(std::forward<U>(u)) {}

 template<class U,
    typename std::enable_if<
      std::is_constructible<T, U>::value &&
      !std::is_convertible<U, T>::value
    , bool>::type = false
  >
  explicit A(U&& u) : t(std::forward<U>(u)) {}

  T t;
};

The shown constructors both use perfect forwarding and they have essentially the same signatures except for one being explicit, the other one not. Furthermore, they are mutually exclusively constrained. In other words: This combination behaves for any destination type T and any argument type U like a single constructor that is either explicit or non-explicit (or no constructor at all).

As Praetorian points out this is exactly how libstdc++ implements it.

If we modify the OPs example accordingly, it also works:

struct S {
  template <typename T,
            typename std::enable_if< std::is_integral<T>::value, bool>::type = false>
  S(T) {}

  template <typename T,
            typename std::enable_if<!std::is_integral<T>::value, bool>::type = false>
  explicit S(T) {}
};
L. F.
  • 19,445
  • 8
  • 48
  • 82
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • 1
    Note that under a strict reading of the standard, the above code could be ill-defined for some types `T`. Such code in `std` isn't a problem, because the implementation of `std` is a *black box* (they are free to do UB or have a technically ill-formed program, so long as the compiler does the right thing in response to said UB/ill formedness). The question is, must a template method of a template class have a at least one valid instantiation for each set of valid template class parameters? The standard is unclear last I checked. – Yakk - Adam Nevraumont Oct 07 '15 at 21:26
  • @Yakk I won't be able to make any real edits for a while but I believe you are correct. I had the inking but I let the thought slip away. – Shafik Yaghmour Oct 07 '15 at 22:20
  • @Yakk I can't seem to find a quote that applies exactly to this case, if I am reading correctly I think the requirement is a valid instantiation not necessarily for each set of parameters. Do you have any defect reports etc...? – Shafik Yaghmour Oct 08 '15 at 03:23
  • 1
    "If no valid specialization can be generated for a template, and that template is not instantiated, the template is ill-formed, no diagnostic required." [temp.res]/8 in n4527. As a guess to why, it appears to be a catch all for "compilers are free to detect template code that could not be valid for any type" (which also opens up the possibility of adding more checks of validity to the standard). The question is, does this apply to template methods of template class instances, or not? – Yakk - Adam Nevraumont Oct 08 '15 at 03:45
  • @Yakk I saw that but it is not clear if it applies to this case or not, I am going to dig into it a little more today. – Shafik Yaghmour Oct 08 '15 at 09:26
  • @Yakk according to a DR resoution, template definitions are never instantiated as template definitions, so if there is a member template of a class template, and we instantiate the surrounding class template, we never instantiate a member template until the member template has all its surrounding parameters resolved. This was the result of clarification for http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1532 . Notice that there can only be a problem for explicit instantiation. For implicit instantiation, the definition of members aren't instantiated anyway. – Johannes Schaub - litb Oct 11 '15 at 14:06
  • @jona the clause actually says the program is ill formed *without* being instantiated tho... – Yakk - Adam Nevraumont Oct 11 '15 at 14:08
  • 1
    @Yakk but it can only be ill-formed if it exists. Prior to doing any instantiation, there only exists the class template, and a member template. If for any combination of T and U there would be a valid instantiation, all is fine. And for the code in the question, this is the case. If you choose `struct B { ... }` for `T` and manage so that it cannot be constructed from anything, that doesn't make the program ill-formed because there is no template beyond `A` that was instantiated as/to a template at this point, so there is no template that can be ill-formed either. – Johannes Schaub - litb Oct 11 '15 at 14:16
5

One way that seems to work with most compilers is to add a dummy parameter to one of the functions, to make them slightly different.

// constructor is explicit if T is integral

struct S {
  template <typename T,
            typename = typename std::enable_if<std::is_integral<T>::value>::type>
  S(T t) {}

  template <typename T,
            typename = typename std::enable_if<!std::is_integral<T>::value>::type,
            typename dummy = void>
  explicit S(T t) {}
};

int main()
{
   S  s1(7);

   S  s2("Hello");    
}

Compiles with MSVC 2015.

Ajay
  • 18,086
  • 12
  • 59
  • 105
Bo Persson
  • 90,663
  • 31
  • 146
  • 203