Why is implicit conversion not applied to templated function parameter? - c++

I'm having an issue with some template stuff that I've narrowed down to the following example (C++17):
template <typename T> struct item {
operator item<const T> () const { return item<const T>(); }
};
void conversionToConstRefWorks (const item<const int> &) { }
template <typename T>
void butNotWhenTemplated (const item<const T> &) { }
int main () {
item<int> i;
item<const int> ci;
// these all compile fine:
conversionToConstRefWorks(ci);
conversionToConstRefWorks(i);
butNotWhenTemplated(ci);
// but this one fails:
butNotWhenTemplated(i);
}
In that example:
item<T> has an implicit conversion operator to item<const T>, and
The conversion seems to work in conversionToConstRefWorks(), but
The conversion seems to be missed in butNotWhenTemplated(), where an item<const int> can be passed just fine but passing an item<int> fails to compile.
Compilation of that example fails (GCC 9.3) with:
g++ --std=c++17 -W -Wall -pedantic -Wno-unused-variable const_interop.cpp -o const_interop
const_interop.cpp: In function ‘int main()’:
const_interop.cpp:54:24: error: no matching function for call to ‘butNotWhenTemplated(item<int>&)’
54 | butNotWhenTemplated(i);
| ^
const_interop.cpp:40:6: note: candidate: ‘template<class T> void butNotWhenTemplated(const item<const T>&)’
40 | void butNotWhenTemplated (const item<const T> &) {
| ^~~~~~~~~~~~~~~~~~~
const_interop.cpp:40:6: note: template argument deduction/substitution failed:
const_interop.cpp:54:24: note: types ‘const T’ and ‘int’ have incompatible cv-qualifiers
54 | butNotWhenTemplated(i);
| ^
The root error seems to be:
types ‘const T’ and ‘int’ have incompatible cv-qualifiers
I understand what that means in a literal sense, but I don't understand why it is happening. My expectation is that the item<int> :: operator item<const int> () const conversion operator would be applied when calling butNotWhenTemplated(i) just as it was applied when calling conversionToConstRefWorks(i), and that int would be selected for T.
My main question is: Why isn't this compiling?
My other question is: For reasons outside the scope of this post, butNotWhenTemplated has to be a template and has to specify <const T> to all item parameters, and I can't explicitly specify template parameters when calling it. Is there a way to make this work with those constraints?
Here it is on ideone (GCC 8.3).

item<int> i;
template <typename T> void butNotWhenTemplated (const item<const T> &) { }
butNotWhenTemplated(i);
According to template argument substitution rules, no T could be found for item<const T> to match item<int>. This fails with an hard error way before any conversion (builtin or user-defined) could be considered.
Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later. However, if deduction succeeds for all parameters that participate in template argument deduction, and all template arguments that aren't deduced are explicitly specified or defaulted, then the remaining function parameters are compared with the corresponding function arguments.

Try this overload:
template <typename T>
void butNotWhenTemplated(const item<const T>&) { }
template <typename T>
void butNotWhenTemplated(const item<T>& x) {
butNotWhenTemplated<const T>(x);
}
Addendum:
You're trying to pass by reference to const, but implicit conversion creates a copy of your object, even in the non-template case. You might want to rethink your design here.

Related

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

This question already has answers here:
SFINAE: std::enable_if as function argument
(2 answers)
Closed 10 months ago.
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&&’
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?.
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.
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;
}
};

Nested Initializer_list template as function parameters [duplicate]

I don't understand why T cannot be deduced in this scenario:
template<class T>
class MyType
{
T * data;
};
class MyOtherType
{
};
template<typename T>
struct MyType_OutArg
{
typedef MyType<T> & type;
};
template<typename T>
void
DoSomething(typename MyType_OutArg<T>::type obj)
{
}
void func(MyType_OutArg<MyOtherType>::type obj)
{
DoSomething(obj);
}
From GCC 4.7.1 with -std=c++14
<source>: In function 'void func(MyType_OutArg<MyOtherType>::type)':
26 : <source>:26:20: error: no matching function for call to 'DoSomething(MyType<MyOtherType>&)'
DoSomething(obj);
^
26 : <source>:26:20: note: candidate is:
19 : <source>:19:1: note: template<class T> void DoSomething(typename MyType_OutArg<T>::type)
DoSomething(typename MyType_OutArg<T>::type obj)
^
19 : <source>:19:1: note: template argument deduction/substitution failed:
26 : <source>:26:20: note: couldn't deduce template parameter 'T'
DoSomething(obj);
^
Compiler returned: 1
Of course the following works:
DoSomething<MyOtherType>(obj);
but i'm unsure why it's necessary. Shouldn't the compiler have enough information?
This is because your case is a Non-deduced contexts.
Cited from http://en.cppreference.com/w/cpp/language/template_argument_deduction:
Non-deduced contexts
In the following cases, the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.
1) The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id
In your case, typename MyType_OutArg<T>::type will not participate in type deduction, and T is not known from elsewhere, thus this template function is ignored.

Template parameter cannot be deduced

I don't understand why T cannot be deduced in this scenario:
template<class T>
class MyType
{
T * data;
};
class MyOtherType
{
};
template<typename T>
struct MyType_OutArg
{
typedef MyType<T> & type;
};
template<typename T>
void
DoSomething(typename MyType_OutArg<T>::type obj)
{
}
void func(MyType_OutArg<MyOtherType>::type obj)
{
DoSomething(obj);
}
From GCC 4.7.1 with -std=c++14
<source>: In function 'void func(MyType_OutArg<MyOtherType>::type)':
26 : <source>:26:20: error: no matching function for call to 'DoSomething(MyType<MyOtherType>&)'
DoSomething(obj);
^
26 : <source>:26:20: note: candidate is:
19 : <source>:19:1: note: template<class T> void DoSomething(typename MyType_OutArg<T>::type)
DoSomething(typename MyType_OutArg<T>::type obj)
^
19 : <source>:19:1: note: template argument deduction/substitution failed:
26 : <source>:26:20: note: couldn't deduce template parameter 'T'
DoSomething(obj);
^
Compiler returned: 1
Of course the following works:
DoSomething<MyOtherType>(obj);
but i'm unsure why it's necessary. Shouldn't the compiler have enough information?
This is because your case is a Non-deduced contexts.
Cited from http://en.cppreference.com/w/cpp/language/template_argument_deduction:
Non-deduced contexts
In the following cases, the types, templates, and non-type values that are used to compose P do not participate in template argument deduction, but instead use the template arguments that were either deduced elsewhere or explicitly specified. If a template parameter is used only in non-deduced contexts and is not explicitly specified, template argument deduction fails.
1) The nested-name-specifier (everything to the left of the scope resolution operator ::) of a type that was specified using a qualified-id
In your case, typename MyType_OutArg<T>::type will not participate in type deduction, and T is not known from elsewhere, thus this template function is ignored.

c++ template instantiation works with int, long, etc. but not float, double, etc

I'm working on a little C++ project, for which I created a nice and handy std::ostream wrapper called Logger.
Inside the class I defined a templated operator:
template<typename T, typename std::enable_if_t<std::is_arithmetic<T>::value, T> = 0>
friend Logger& operator<<(Logger& logger, const T& logMessage) {
return logger << std::to_string(logMessage); // there is also an operator overload which takes a std::string& as argument.
}
When the template is instanciated with an integral type, like int or long, the template works like a charm.
So the statement
someLoggerInstance << 123 << 456ULL << "\n";
compiles just fine.
However if I change the statement to
someLoggerInstance << 12.3 << "\n";
the compiler shouts at me that the template could not be instantiated.
The error message from the compiler:
Candidate template ignored: substitution failure [with T = double]: a non-type template parameter cannot have type 'typename std::enable_if_t::value, double>' (aka 'double')
Where is the problem?
I tried to replace std::is_arithmetic<T> with std::is_integral<T> and add a second templated function using std::is_floating_point<T>, but this does not change anything, the integral template is instantiated, but the floating point template is not, leading to the same error message as above.
If I create an additional overload for the type double
friend Logger& operator<<(Logger& logger, const double& logMessage) {
return logger << std::to_string(logMessage);
}
the problem gets even worse, since then the complier, trying to pass the "\n" at the end, annoys me with the following messages:
Candidate function not viable: no known conversion from 'const unsigned char [1]' to 'const double' for 2nd argument
and
Candidate function not viable: no known conversion from 'const unsigned char [1]' to 'const std::string' (aka 'const basic_string, allocator >') for 2nd argument
Any help or advise would be nice!
Thank you!
Edit
Thanks for the quick help.
Now changed the template like this:
template<typename T, typename TEnable = std::enable_if_t<std::is_arithmetic<T>::value && !std::is_same<T, char>::value, T>>
friend Logger& operator<<(Logger& logger, const T& logMessage);
Update template to use type template parameter:
template
<
typename T
, typename TEnabled = typename std::enable_if_t<std::is_arithmetic<T>::value, T>
>
friend Logger & operator <<(Logger & logger, const T & logMessage) {
return logger << std::to_string(logMessage);
}
When T is a double, this thing std::enable_if_t<std::is_arithmetic<T>::value, T> resolves to a double as well.
But non-type template parameters are explicitly disallowed to be floating point types. The correct minimal example for your problem is this:
template<double d> // this is ill-formed
void foo() {}
The rationale is that templates are specialized according to the exact value of the non-type argument. Which is an issue for types to which comparing for direct equality is problematic.
Just have std::enable_if_t resolve to a valid type, such as a pointer or an int that you initialize with 0.
To solve your first problem an alternative SFINAE is to use std::enable_if_t to allow substitution failure for the return type...
template <typename T>
std::enable_if_t<std::is_arithmetic<T>::value, Logger&>
friend operator<<(Logger& logger, const T& logMessage)
{
return logger << std::to_string(logMessage);
}
Your second problem is because std::is_arithmetic<char>::value is true so you need to disqualify that case.

Calling a function with an argument implicitly convertible to an object of template class

Consider the following example (godbolt):
template <typename T>
struct S {
S(int) {}
};
template <typename T>
void f(S<T>, T) {}
int main() {
f(1, 2);
}
Compiling it gives the following error:
<source>: In function 'int main()':
10 : <source>:10:11: error: no matching function for call to 'f(int, int)'
f(1, 2);
^
7 : <source>:7:6: note: candidate: template<class T> void f(S<T>, T)
void f(S<T>, T) {}
^
7 : <source>:7:6: note: template argument deduction/substitution failed:
10 : <source>:10:11: note: mismatched types 'S<T>' and 'int'
f(1, 2);
^
Making S a non-template makes the example compile.
Why doesn't this code compile despite an implicit conversion from int to S<T>?
template functions are not functions. They are templates for writing functions.
template <typename T>
void f(S<T>, T) {}
this is a template for writing a function given a type T.
Now, C++ will in some situations attempt to deduce T for you. What it does is pattern match on every argument (at once).
If any argument fails to find a match, or the deduced types are inconsistent or incomplete, the deduction fails. No conversion or partial match is attempted. If matches are found, they are added as candidates to the overloads in consideration (with some rules here), then overload resolution kicks in.
At overload resolution time conversion is considered. At template type deduction it is not other than conversion to base.
In your case, S<T> cannot deduce the type T from 1. So deduction simply fails. We never reach overload resolution where conversions are considered.
As it happens you can block an argument from being considered during deduction:
template<class T>struct tag_t{using type=T;}:
template<class T>using block_deduction=typename tag_t<T>::type;
template <typename T>
void f(block_deduction<S<T>>, T) {}
and now your main compiles.