【问题标题】:Is there a way to specify a precedence among user defined conversions?有没有办法在用户定义的转换中指定优先级?
【发布时间】:2014-09-26 11:44:24
【问题描述】:

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

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

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 而不是编译失败。

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

【问题讨论】:

  • 我可以重写foo?
  • 1.你也可以修改模板吗? 2. 您能否向我们展示一个不符合您要求的模板示例?
  • 在决定您要调用哪种风格的foo 时,区别因素是什么?
  • @JohnDibling:如果foos 存在的话,我总是更喜欢foo(X) 而不是foo(Y)(并非总是如此)。如果只存在foo(Y),我希望转换为Y。如果只存在foo(X),那么当然转换为X
  • 听起来像是 SFINAE 的案例

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


【解决方案1】:

您可以使用以下模式执行“优先调用”之类的操作:

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,否则这不能编译为类型不兼容。

请注意,您甚至可以将“优先级”概括为

template<size_t N> struct P: P<N+1> {}
template<> struct P<10> {}

声明 SFINAE 函数采用 P&lt;1&gt;P&lt;2&gt; 等直到 P&lt;10&gt;,并使用 P&lt;0&gt;() 发出根调用

【讨论】:

    【解决方案2】:

    这是一个小系统,它可以智能地将变量转换为 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)) );
    }
    

    如果可能,fooA 转换为 X,如果不能转换为 Y

    我们可以编写一个完整的系统,将foo 重载的描述传递给像types&lt; types&lt;X,Y&gt; &gt; 这样的包和foo 的重载集到一些魔术代码,它会输出一个调度程序,但是那将是过度工程。

    live example

    这使用了 C++11 特性。使用 C++14,我们可以在 smart_cast 中删除一些措辞。

    设计非常简单。我们创建了一个 types 包来处理类型包(只是样板文件)。

    然后我们的details::smart_cast_t 有一个备用基础专业化,它只是将我们的U 转换为U。如果我们可以将U 转换为第一种类型,我们就会这样做。否则,我们会在父类型上递归,可能会终止于基础特化。

    我把它隐藏了,所以我们的公共函数很简单——它是smart_cast&lt; type1, type2, type3, etc &gt;( expression )。我们不必传递U 表达式的类型,因为我们推断它,然后将其传递到要完成的工作的细节中。

    在运行时,这会简化为一个隐式强制转换:以上所有内容都是在编译时完成的。

    唯一的缺点是这可能会导致某些编译器出现警告,因为我们使用隐式转换。将static_cast&lt;T0&gt; 添加到smart_cast_t 的第一个非基本特化中以避免这种情况。

    我不必要地从true_typefalse_type 继承了smart_cast_t。这意味着smart_cast_t&lt;U, types&lt;Ts...&gt;&gt;::value 的值将告诉您U 是否转换为Ts... 中的任何一个,或者只是作为U 单独存在。

    调用者负责右值与左值类别。如果转换失败,如果传递了一个右值,它将返回一个U 而不是U&amp;&amp;。回退 - 到 U - 我认为会生成更好的错误消息,但如果您希望 smart_cast&lt;int, double&gt;(std::string("hello")) 无法编译而不是返回 std::string,只需从 @987654357 的基本特化中删除 operator() @。

    说起来,我也不适合smart_cast&lt;int, double&gt;("")。可能需要一些typename std::decay&lt;U&gt;::types 或其中的一些东西。

    【讨论】:

      【解决方案3】:

      是的,明确地转换它:

      foo(static_cast<Y>(z));
      

      【讨论】:

      • 这是不可能的。客户端代码是一个模板代码,它不知道 1) 哪些重载可用于 foo 和 2) 哪些强制转换可用于 z。这都是由模板参数决定的。我已经澄清了我的问题。
      猜你喜欢
      • 2019-08-18
      • 2020-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-21
      • 1970-01-01
      相关资源
      最近更新 更多