Search code examples
c++c++11templatesimplicit-conversiontemplate-argument-deduction

Template argument substitution fails and implicit conversion is not done


#include <type_traits>

template<bool Const>
struct view_tpl {
    using value_type = std::conditional_t<Const, const int, int>;
    value_type* ptr;

    view_tpl() = default;
    view_tpl(const view_tpl<false>& other) : ptr(other.ptr) { }
};

using view = view_tpl<false>;
using const_view = view_tpl<true>;

void read(const const_view& vw) { }

int main() {
    view vw;
    read(vw);
}

This code defines a const and a non-const view type, both as aliases to a view_tpl<Const> template. It should be such that view is implicitly convertible to const_view, but not the other way around.

It Const is true, the defined copy-constructor enables this, and the compiler generates an additional default copy-constructor. If Const is false the defined copy-constructor replaces the default copy-constructor.

This implicit conversion should happen when f(vw) is called.

It works correctly in the above code.


But if I add an argument to the templates (int N), and turn f and the two type aliasses into templates, it no longer works:

#include <type_traits>

template<int N, bool Const>
struct view_tpl {
    using value_type = std::conditional_t<Const, const int, int>;
    value_type* ptr;

    view_tpl() = default;
    view_tpl(const view_tpl<N, false>& other) : ptr(other.ptr) { }
};

template<int N> using view = view_tpl<N, false>;
template<int N> using const_view = view_tpl<N, true>;

template<int N>
void read(const const_view<N>& vw) { }

int main() {
    view<0> vw;
    read(vw);
}

Instead of doing the conversion of view_tpl<0, true> to view_tpl<0, false>, the compiler only tries a direct template substitution and fails:

main.cpp: In function 'int main()':
main.cpp:20:12: error: no matching function for call to 'read(view<0>&)'
   20 |     read(vw);
      |            ^
main.cpp:16:6: note: candidate: 'template<int N> void read(const_view<N>&)'
   16 | void read(const const_view<N>& vw) { }
      |      ^~~~
main.cpp:16:6: note:   template argument deduction/substitution failed:
main.cpp:20:12: note:   template argument 'false' does not match 'true'
   20 |     read(vw);
      |            ^

Is there a way to make this work without changing too much of the code? (The real code is more complex than this example)


Solution

  • Unfortunately, implicit conversions won't be considered in template argument deduction.

    Type deduction does not consider implicit conversions (other than type adjustments listed above): that's the job for overload resolution, which happens later.

    You can specify the template argument explicitly to by pass the deduction, then implicit conversion would work fine later. e.g.

    view<0> vw;
    read<0>(vw);
    

    Or apply explicit conversion and wrap it into a helper.

    template<int N>
    void read(const view<N>& vw) { read(static_cast<const_view<N>>(vw)); }