有没有办法在用户定义的转换中指定优先级?

时间:2014-09-26 11:44:24

标签: c++ templates c++11 implicit-conversion ambiguous

免责声明:我知道经常不鼓励使用用户定义的隐式转换。但是,在我们的项目中,我们需要对各种模板类进行这些转换才能很好地相互协作。

我需要定义用户定义转换的优先级,例如:

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
}

这不会编译,因为从ZXY的转换不明确。 有没有办法解决这种歧义。即,我可以以某种方式告诉编译器:如果XY有重载函数,则更喜欢将Z强制转换为X而不是无法编译。< /强>

我知道没有简单的方法来指定它。但也许一些我不知道的模板魔术和包装模板结构可能会成功。我被允许更改客户端代码。我不能使用显式强制转换,因为客户端代码是模板化代码,不知道类型Zfoo的可用重载。

3 个答案:

答案 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的重载集传递给某些魔术代码,吐出一个调度员,但这将超过工程师。

live example

这使用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_ttrue_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));