具有两个不同类型参数的隐式模板类型推导
Implicit template type deduction with two arguments of different types
假设以下情况:
类型A
和类型B
,B
可以隐式转换为A
,反之则不正确。
我有一个功能
template<class T>
void do_stuff(T a, T b);
我想这样调用所述函数:
do_stuff(A{}, B{});
这里的问题是编译器无法推断类型而是说:
template argument deduction/substitution failed
我可以这样调用我的函数:
do_stuff<A>(A{}, B{});
但这对用户来说更烦人。
或者我可以这样做:
template<class T, class M>
void do_stuff(T a, M b);
但随后 b 继续其快乐的方式成为 B 类型(通过先前的调用)。
理想情况下我想要这样的东西:
template<class T, class M = T>
void do_stuff(T a, M b);
或:
template<class T@INSERT MAGIC SO THAT T IS DEDUCED AS BEING THE TYPE OF ARGUMENT NR 1@>
void do_stuff(T a, T b);
这样的事情可能吗?
这当然是可能的,只是需要一点授权。通过指定您始终希望推断类型为第一个参数的类型,您已经使问题变得非常简单,因此我们需要做的就是向编译器提供一点提示。
template <class T>
void do_stuff_impl(T a, T b) {
cout << "Doing some work..." << endl;
}
template <class T, class S>
void do_stuff(T a, S b) {
do_stuff_impl<T>(a, b);
}
现在用户可以使用任意两个参数调用 do_stuff
,C++ 将尝试隐式转换第二个参数以匹配第一个参数的类型。如果转换无效,您将收到模板实例化错误。在 GCC 上,它表示 cannot convert ‘b’ (type ‘A’) to type ‘B’
,非常准确而且切题。任何称职的编译器都能够内联该委托调用,因此开销应该可以忽略不计。
在非推导上下文中包装 b
。这样,只有 a
会被推导出来,并且 b
必须转换为该类型。
template <class T> struct dont_deduce { using type = T; };
template <class T> using dont_deduce_t = typename dont_deduce<T>::type;
template<class T>
void do_stuff(T a, dont_deduce_t<T> b);
另一种方式,试图以声明的方式表达意图:
#include <type_traits>
// a B
struct B{};
// an A can be constructed from a B
struct A{
A() {};
A(B) {};
};
// prove that A is constructible from B
static_assert(std::is_convertible<B, A>::value, "");
// enable this function only if a U is convertible to a T
template<
// introduce the actors
class T, class U,
// declare intent
std::enable_if_t<std::is_convertible<U, T>::value>* = nullptr
>
void do_stuff(T, U)
{
}
int main()
{
// legal
do_stuff(A{}, B{});
// does not compile
// do_stuff(B{}, A{});
}
更新:
要强制转换,可以使用 lambda:
// enable this function only if a U is convertible to a T
template<class T, class U,
std::enable_if_t<std::is_convertible<U, T>::value>* = nullptr
>
void do_stuff(T a, U b)
{
return[](T& a, T b) -> decltype(auto)
{
}(a, b);
}
C++11 中有答案:std::common_type
http://en.cppreference.com/w/cpp/types/common_type
template<typename A>
void f_impl(A a, A b)
{
}
template<typename A, typename B>
void f(A a, B b)
{
f_impl<typename std::common_type<A, B>::type>(a, b);
}
struct Z
{
};
struct W
{
operator Z();
};
int main()
{
f(1u, 1l); //work
f(W{}, Z{});
f(Z{}, W{}); //and this work too
}
假设以下情况:
类型A
和类型B
,B
可以隐式转换为A
,反之则不正确。
我有一个功能
template<class T>
void do_stuff(T a, T b);
我想这样调用所述函数:
do_stuff(A{}, B{});
这里的问题是编译器无法推断类型而是说:
template argument deduction/substitution failed
我可以这样调用我的函数:
do_stuff<A>(A{}, B{});
但这对用户来说更烦人。
或者我可以这样做:
template<class T, class M>
void do_stuff(T a, M b);
但随后 b 继续其快乐的方式成为 B 类型(通过先前的调用)。
理想情况下我想要这样的东西:
template<class T, class M = T>
void do_stuff(T a, M b);
或:
template<class T@INSERT MAGIC SO THAT T IS DEDUCED AS BEING THE TYPE OF ARGUMENT NR 1@>
void do_stuff(T a, T b);
这样的事情可能吗?
这当然是可能的,只是需要一点授权。通过指定您始终希望推断类型为第一个参数的类型,您已经使问题变得非常简单,因此我们需要做的就是向编译器提供一点提示。
template <class T>
void do_stuff_impl(T a, T b) {
cout << "Doing some work..." << endl;
}
template <class T, class S>
void do_stuff(T a, S b) {
do_stuff_impl<T>(a, b);
}
现在用户可以使用任意两个参数调用 do_stuff
,C++ 将尝试隐式转换第二个参数以匹配第一个参数的类型。如果转换无效,您将收到模板实例化错误。在 GCC 上,它表示 cannot convert ‘b’ (type ‘A’) to type ‘B’
,非常准确而且切题。任何称职的编译器都能够内联该委托调用,因此开销应该可以忽略不计。
在非推导上下文中包装 b
。这样,只有 a
会被推导出来,并且 b
必须转换为该类型。
template <class T> struct dont_deduce { using type = T; };
template <class T> using dont_deduce_t = typename dont_deduce<T>::type;
template<class T>
void do_stuff(T a, dont_deduce_t<T> b);
另一种方式,试图以声明的方式表达意图:
#include <type_traits>
// a B
struct B{};
// an A can be constructed from a B
struct A{
A() {};
A(B) {};
};
// prove that A is constructible from B
static_assert(std::is_convertible<B, A>::value, "");
// enable this function only if a U is convertible to a T
template<
// introduce the actors
class T, class U,
// declare intent
std::enable_if_t<std::is_convertible<U, T>::value>* = nullptr
>
void do_stuff(T, U)
{
}
int main()
{
// legal
do_stuff(A{}, B{});
// does not compile
// do_stuff(B{}, A{});
}
更新:
要强制转换,可以使用 lambda:
// enable this function only if a U is convertible to a T
template<class T, class U,
std::enable_if_t<std::is_convertible<U, T>::value>* = nullptr
>
void do_stuff(T a, U b)
{
return[](T& a, T b) -> decltype(auto)
{
}(a, b);
}
C++11 中有答案:std::common_type
http://en.cppreference.com/w/cpp/types/common_type
template<typename A>
void f_impl(A a, A b)
{
}
template<typename A, typename B>
void f(A a, B b)
{
f_impl<typename std::common_type<A, B>::type>(a, b);
}
struct Z
{
};
struct W
{
operator Z();
};
int main()
{
f(1u, 1l); //work
f(W{}, Z{});
f(Z{}, W{}); //and this work too
}