This actually compiles and works, but it's unclear to me why.
#include <iostream>
template <class T>
class LikeA
{
T m_val{};
public:
LikeA() = default;
explicit LikeA(T iv): m_val(std::move(iv)) {}
LikeA(LikeA<T> const &) = default;
LikeA(LikeA<T> &&) noexcept = default;
~LikeA() noexcept = default;
operator T const &() const { return m_val; }
LikeA<T> &operator=(T nv) { m_val = std::move(nv); return *this; }
LikeA<T> &operator=(LikeA<T> const &n) { m_val = n.m_val; return *this; }
LikeA<T> &operator=(LikeA<T> &&n) { m_val = std::move(n.m_val); return *this; }
};
template <class T>
T f (LikeA<T> i)
{
return i;
}
int main()
{
std::cout << f(LikeA{3.1415927}) << '\n'; // No template argument? Not a syntax error?
return 0;
}
I was previously calling f
like f(3.1415927)
before I let a lint checker talk me into making one of LikeA
s constructors explicit
. After that, of course, it couldn't implicitly convert the constant to a LikeA
. If you just add braces (i.e. f({3.1415927})
the compiler still doesn't know what to select.
In my full code the actual template argument is a lot more verbose, so just for grins I put the template name LikeA
in front of the brace initializers, fully expecting a syntax error.
To my surprise, it compiled and ran.
Since this was MSVC, at first I though it was just Microsoft lulling me into a sense of false security. But I tested it against several compilers (gcc, clang, zigcc) in Compiler Explorer, and it works on all of them.
How does C++ select the correct template specialization? On the surface, argument-dependent lookup would seem to be the answer, but notice there are no angle brackets, and the template doesn't have a default argument. I definitely remember this being a syntax error at some point in the past.
(Function template specialization without templated argument doesn't answer this because OP actually specifies the arguments).
The cppreference on function template arguments has a quick aside about omitting <>
but this is a class template. The syntax here appears to require the angle brackets all the time.
Since C++17, compiler can automatically deduce the argument type of a template by using class template argument deduction (CTAD). You can skip defining the templates arguments explicitly if the constructor is able to deduce all template parameters.
So you simply write
int main()
{
std::vector v{2, 4, 6, 8}; // same as std::vector<int>
std::list l{1., 3., 5.}; // same as std::list<double>
std::pair p{false, "hello"}; // same as std::pair<bool, const char *>
std::cout << typeid(v).name() << std::endl;
std::cout << typeid(l).name() << std::endl;
std::cout << typeid(p).name() << std::endl;
}
Under MSVC, it produces the following output
class std::vector<int,class std::allocator<int> >
class std::list<double,class std::allocator<double> >
struct std::pair<bool,char const * __ptr64>
Kindly refer CTAD for more details.