免责声明:我知道经常不鼓励使用用户定义的隐式转换。但是,在我们的项目中,我们需要对各种模板类进行这些转换才能很好地相互协作。
我需要定义用户定义转换的优先级,例如:
struct X{}
struct Y{}
struct Z{
operator X(){...}
operator Y(){...}
}
void foo(X x){...}
void foo(Y y){...}
// somewhere in some template client code
...{
Z z = ...;
...
foo(z); // WILL NOT COMPILE
}
这不会编译,因为从Z
到X
或Y
的转换不明确。 有没有办法解决这种歧义。即,我可以以某种方式告诉编译器:如果X
和Y
有重载函数,则更喜欢将Z
强制转换为X
而不是无法编译。< /强>
我知道没有简单的方法来指定它。但也许一些我不知道的模板魔术和包装模板结构可能会成功。我被允许更改客户端代码。我不能使用显式强制转换,因为客户端代码是模板化代码,不知道类型Z
和foo
的可用重载。
答案 0 :(得分:1)
您可以使用以下模式执行类似&#34;优先通话&#34;之类的操作:
struct P2 {};
struct P1: P2 {};
template<class A>
void foo(A x, P1, typename std::common_type<X,A>::type* =nullptr)
{ foo(static_cast<X>(x)); }
template<class A>
void foo(A y, P2, typename std::common_type<Y,A>::type* =nullptr)
{ foo(static_cast<Y>(y)); }
template<class A> void foo(A a) { foo(a,P1()); }
将P2作为P1的基础并使用P1进行调用,如果common_type可以编译,则第一个版本进行。如果它无法编译,第一个版本就像不存在(SFINAE),第二个版本就是如此。如果它也不能编译...如果A只是X或只是Y,则调用相应的原始foo,否则这不能编译为不兼容的类型。
请注意,您甚至可以概括&#34;优先级&#34;如
template<size_t N> struct P: P<N+1> {}
template<> struct P<10> {}
声明SFINAE函数将P<1>
,P<2>
等转发至P<10>
,并将根调用置于P<0>()
答案 1 :(得分:1)
这是一个智能将变量转换为类型序列Ts...
的小系统,这样变量隐式转换为的列表Ts...
中的第一个元素就是所选的:
namespace details {
template<class...>struct types{using type=types;};
template<class U, class Types, class=void>
struct smart_cast_t:std::false_type {
using type=U;
template<class A>
U operator()(A&& a)const{return std::forward<A>(a);}
};
template<class U, class T0, class...Ts>
struct smart_cast_t<
U, types<T0, Ts...>,
typename std::enable_if<std::is_convertible<U, T0>::value>::type
>:std::true_type
{
using type=T0;
template<class A>
T0 operator()(A&& a)const{return std::forward<A>(a);}
};
template<class U, class T0, class...Ts>
struct smart_cast_t<
U, types<T0, Ts...>,
typename std::enable_if<!std::is_convertible<U, T0>::value>::type
>:smart_cast_t< U, types<Ts...> >
{};
}
template<class... Ts, class U>
auto smart_cast( U&& u )
-> decltype(details::smart_cast_t< U, details::types<Ts...> >{}( std::forward<U>(u) ))
{
return details::smart_cast_t< U, details::types<Ts...> >{}( std::forward<U>(u) );
}
现在,我们的想法是,我们现在可以修改foo
,如下所示:
void foo_impl(X);
void foo_impl(Y);
template<class A>
void foo(A&& a) {
foo_impl( smart_cast<X, Y>(std::forward<A>(a)) );
}
如果可能的话,和foo
会将A
投放到X
,如果不是Y
,则会{。}}。
我们可以编写一个完整的系统,在这个系统中,我们将foo
这样的包中types< types<X,Y> >
的重载描述和foo
的重载集传递给某些魔术代码,吐出一个调度员,但这将超过工程师。
这使用C ++ 11功能。使用C ++ 14,我们可以在smart_cast
中删除一些verbage。
设计非常简单。我们创建了一个types
包来处理类型的捆绑(只是样板)。
然后我们的details::smart_cast_t
有一个后备基础专精,只将我们的U
转换为U
。如果我们可以将U
转换为第一种类型,我们就会这样做。否则,我们递归我们的父类型,可能终止于基本特化。
我隐藏了细节,所以我们的公共功能很简单 - 它是smart_cast< type1, type2, type3, etc >( expression )
。我们不必传递U
表达式的类型,因为我们推导出它,然后将其传递给要完成工作的细节。
在运行时,这会减少为单个隐式转换:以上所有内容都是在编译时完成的。
关于唯一的缺点是,这会导致某些编译器发出警告,因为我们使用隐式转换。将static_cast<T0>
添加到smart_cast_t
的第一个非基本专精,以避免这种情况。
我不必要地从smart_cast_t
和true_type
继承了false_type
。这意味着smart_cast_t<U, types<Ts...>>::value
的值会告诉您U
是否已转换为Ts...
中的任何一个,或者仅作为U
保留。
调用者负责rvalue vs lvalue类别。如果强制转换失败,如果传递了右值,它将返回U
而不是U&&
。回退 - U
- 我认为会生成更好的错误消息,但如果您希望smart_cast<int, double>(std::string("hello"))
无法编译而不是返回std::string
,只需删除operator()
来自smart_cast_t
的基本专业化。
说到这一点,我也不适合smart_cast<int, double>("")
。可能需要一些typename std::decay<U>::type
或其他东西。
答案 2 :(得分:0)
是的,明确地说明:
foo(static_cast<Y>(z));