Why is implicit conversion not applied to templated function parameter?
Asked Answered
K

2

6

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).

Karrykarst answered 22/12, 2020 at 0:0 Comment(2)
But in the name of the standard, why does this work? (instead of Item<T> I use T*)Overscrupulous
@RinKaenbyou Unless I'm misreading/misunderstanding, I think it's because there, the int *int const * const & conversion isn't done within a template parameter (unlike Item<T>Item<const T> -- yours is more similar in spirit to Item<T>const Item<T>). E.g. T and const T are the same type differing only in cv qualifiers, whereas Item<T> and Item<const T> are completely different types since the template parameters are different, and then something about the deduction rules let that be ok. Maybe. Otoh I have no idea what I'm talking about.Karrykarst
R
9
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.

Rowden answered 22/12, 2020 at 0:12 Comment(3)
Well... that stinks. It's too bad there's no way to tell the compiler which conversions are possible before deduction happens. :(Karrykarst
@JasonC You can always resort to tricks: coliru.stacked-crooked.com/a/b34585169034b311Rowden
I do not dislike that idea. I'll play with it shortly.Karrykarst
H
4

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.

Hajji answered 22/12, 2020 at 0:33 Comment(6)
My real code has multiple parameters that may vary in const-ness but must always have the same base type; I was able to do something like your idea with ideone.com/8A1KC9.Karrykarst
It doesn't give recursion for me (using Visual C++). AFAIK overload resolution should prefer the const qualified version. Perhaps there is something different in your code?Hajji
I think I did do something different in my code; because I just tried yours again and it's fine. Now trying to remember what I did to make that recurse is bugging the crap out of me.Karrykarst
ideone.com/YbaKSz does get stuck, though, which is the same as my link in my first comment but with the functions named the same. Can't really wrap my head around why. I dunno, my head hurts, but in any case I like this approach.Karrykarst
test(i, c, c) and test(c, c, c) work, so I think the compiler thinks you're only trying to specify the first type, doing it this way work: ideone.com/Yg1YC8Hajji
I like it. I'll get the hang of this eventually. Thanks.Karrykarst

© 2022 - 2024 — McMap. All rights reserved.