Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fallback variadic constructor - why does this work?

In answering this question about trying to construct a variadic forwarding reference constructor that should only be called if no other constructor is valid. That is, if there was a:

C(const char*, size_t) { }                     // 1
template <typename... T, ???> C(T&&... ) { }   // 2

We'd want C c1{"abc", 2}; to call (1), despite the required conversion, but C c2{1, 2, 3}; to call (2), as (1) cannot apply.

I proposed the following solution:

template <typename... T,
          typename = std::enable_if_t<!std::is_constructible<C, T&&...>::value>
           >
C(T&&... ) { }

And by proposed, I mean, I tried it and was surprised to discover that it actually works. It compiles and does exactly what I had hoped for on both gcc and clang. However, I am at a loss to explain why it works or even if it's actually supposed to work and gcc and clang are both just being particularly accommodating. Is it? Why?

like image 903
Barry Avatar asked Oct 07 '15 16:10

Barry


People also ask

Why do we need a default constructor in C++?

They are used to initialize member objects. If default values are supplied, the trailing arguments can be omitted in the expression list of the constructor. Note that if a constructor has any arguments that do not have default values, it is not a default constructor.

Does constructor return any value in C++?

A return statement in the body of a constructor cannot have a return value.

How do you call a default constructor in C++?

base a declares a variable a of type base and calls its default constructor (assuming it's not a builtin type). base a(); declares a function a that takes no parameters and returns type base .

Which data type uses a constructor that accepts no parameters?

A constructor that takes no parameters is called a parameterless constructor. Parameterless constructors are invoked whenever an object is instantiated by using the new operator and no arguments are provided to new .


1 Answers

The issue with your code is that we just instantiated is_constructible in a context where it gets the answer wrong. Any kind of caching in the template code is likely to result in bugs -- try printing is_constructible on the same parameters after you call the constructor! It is likely to get it wrong.

Live example of how it can go wrong. Notice it claims C cannot be constructed from an int&, despite having done so on the previous line.

struct C {
  C(const char*, size_t) {}
  template <class... Ts,
    typename = std::enable_if_t<!std::is_constructible<C, Ts&&...>::value>
  >
  C(Ts&&... ) { }
};

int main() {
  int a = 0;
  C x{a};
  std::cout << std::is_constructible<C, int&>{} << '\n';
}

oops.

I suspect this might be an ODR violation -- the two definitions of is_constructible have different types at different spots? Or maybe not.

Solution to the original problem that does not have this issue also posted.

like image 98
Yakk - Adam Nevraumont Avatar answered Oct 23 '22 19:10

Yakk - Adam Nevraumont