Why does an optional argument in a template constructor for enable_if help the compiler to deduce the template parameter? [duplicate]
Asked Answered
P

3

8

The minimal example is rather short:

#include <iostream>
#include <array>
#include <type_traits>

struct Foo{
    //template <class C>
    //Foo(C col, typename std::enable_if<true,C>::type* = 0){
    //    std::cout << "optional argument constructor works" << std::endl;
    //}
    template <class C>
    Foo(typename std::enable_if<true, C>::type col){
        std::cout << "no optional argument constructor works NOT" << std::endl;
    }
};

int main()
{
    auto foo = Foo(std::array<bool,3>{0,0,1});
}

The first constructor works as expected. However the second constructor does not compile and I get

error: no matching function for call to ‘Foo::Foo(std::array)’

However the given explanation

note: template argument deduction/substitution failed

does not help, as std::enable_if<true, C>::type should be C and such the first argument in both constructors should look exactly the same to the compiler. I'm clearly missing something. Why is the compiler behaving differently and are there any other solution for constructors and enable_if, which do not use an optional argument?

Complete error message:

main.cpp:18:45: error: no matching function for call to ‘Foo::Foo(std::array)’
   18 |     auto foo = Foo(std::array<bool,3>{0,0,1});
      |                                             ^
main.cpp:11:5: note: candidate: ‘template Foo::Foo(typename std::enable_if::type)’
   11 |     Foo(typename std::enable_if<true, C>::type col){
      |     ^~~
main.cpp:11:5: note:   template argument deduction/substitution failed:
main.cpp:18:45: note:   couldn’t deduce template parameter ‘C’
   18 |     auto foo = Foo(std::array<bool,3>{0,0,1});
      |                                             ^
main.cpp:5:8: note: candidate: ‘constexpr Foo::Foo(const Foo&)’
    5 | struct Foo{
      |        ^~~
main.cpp:5:8: note:   no known conversion for argument 1 from ‘std::array’ to ‘const Foo&’
main.cpp:5:8: note: candidate: ‘constexpr Foo::Foo(Foo&&)’
main.cpp:5:8: note:   no known conversion for argument 1 from ‘std::array’ to ‘Foo&&’
Predispose answered 6/5, 2022 at 9:34 Comment(1)
these days (before C++20) you put the enable_if as an optional template argument (ie in the template part), that is a better way to get it out of the way and not even try to do what you did.Doroteya
B
8

Template argument deduction does not work this way.

Suppose you have a template and a function using a type alias of that template:

template <typename T>
struct foo;

template <typename S>
void bar(foo<S>::type x) {}

When you call the function, eg foo(1) then the compiler will not try all instantiations of foo to see if any has a type that matches the type of 1. And it cannot do that because foo::type is not necessarily unambiguous. It could be that different instantiations have the same foo<T>::type:

template <>
struct foo<int> { using type = int; };

template <>
struct foo<double> { using type = int; };

Instead of even attempting this route and potentially resulting in ambiguity, foo<S>::type x is a nondeduced context. For details see What is a nondeduced context?.

Brubaker answered 6/5, 2022 at 9:52 Comment(0)
M
6

Template argument deduction fails because C appears in a Non-deduced context. The linked page lists

The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id

as a non-deduced context.

They also mention another example further down:

For example, in A<T>::B<T2>, T is non-deduced because of rule #1 (nested name specifier), and T2 is non-deduced because it is part of the same type name, but in void(*f)(typename A<T>::B, A<T>), the T in A<T>::B is non-deduced (because of the same rule), while the T in A<T> is deduced.

Moonshine answered 6/5, 2022 at 9:50 Comment(0)
A
2

The other answers already explain why argument deduction did not work here. If you want to enable_if your constructor, you can simply put the condition in the template list like this:

struct Foo{
    //                      your condition here ---v
    template <class C, typename std::enable_if_t< true >* = nullptr>
    Foo(C col) {
        std::cout << "constructor" << std::endl;
    }
};
Administrator answered 6/5, 2022 at 10:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.