【问题标题】:Conditionally compile a conversion operator based on numeric template parameter基于数字模板参数有条件地编译转换运算符
【发布时间】:2017-10-24 09:52:40
【问题描述】:

我有一个template <bool P> class Foo,里面有很多代码。我希望能够将Foo<true>'s 转换成Foo<false>'s,即有一个operator Foo<false>() 方法。但是编译器不喜欢 Foo 存在的这种方法,它只喜欢Foo<true>,并警告“不会为隐式或显式转换调用运算符”(GCC 5.4.x)

我似乎不能为此使用 SFINAE:std::enable_if 适用于类型;并且我尝试过的值变体(真正的案例有 value 而不是 type 成员)也没有帮助。

我怎样才能让这个运算符只为Foo<false> 编译(除了专门针对Foo<false> 不同并复制我的所有代码)?

到目前为止,我最好的尝试是:

template <bool P> class Foo {
    // etc. etc.
    template <bool OtherValue>
    operator Foo<OtherValue>()
    {
        static_assert(OtherValue, "You should not be using this conversion!");
        // conversion code here
        return Foo<false>(args,go,here);
    }
}

【问题讨论】:

  • 为什么是转换运算符而不是构造函数?
  • @André - 这并不能解决问题,真的。它仅确保在复制 foo&lt;false&gt; 时编译器会抱怨重复定义(假设还存在复制 c'tor)。
  • @André:我不认为我可以让 ctor 只存在于Foo&lt;false&gt;。但是 - 这是一个想法,我想。

标签: c++ class templates typecast-operator


【解决方案1】:

我怎样才能让这个运算符只为Foo&lt;false&gt; 编译(除了专门针对Foo&lt;false&gt; 不同并复制我的所有代码)?

template <bool P> 
struct Foo 
{
    template <bool P2 = P, typename = std::enable_if_t<P2 == true>>
    operator Foo<false>()
    {
        return {};
    }
};

使用P2 = P 会将enable_if_t 的评估延迟到实际使用转换运算符的时间(而不是类实例化)


如果我们尝试简单地写:

template <typename = std::enable_if_t<P == true>>
operator Foo<false>()
{
    return {};
}

std::enable_if_t&lt;P == true&gt; 将在 Foo&lt;P&gt; 的实例化期间进行评估,因为在实例化成员函数时不会发生替换。当 Foo 被实例化时会发生替换 - 因此 SFINAE 无法发生(因为尚未设置任何重载解决方案)

通过添加bool P2 = P 默认参数,我们将替换延迟到转换运算符的实例化。这发生在重载解决期间,因此 SFINAE 可以发生。

这个答案比我解释得更好:https://stackoverflow.com/a/13401982/598696

【讨论】:

  • 啊,你真狡猾!但是为什么会延迟评估呢?你能详细说明一下吗?
  • @einpoklum:添加了更多解释和链接。
【解决方案2】:

您的最佳尝试实际上并不遥远。您需要将转换运算符转换为模板,以便 SFINAE 可以工作。仅在 P 为真的情况下强制执行:

template<bool P>
struct foo {
    template<bool V = P, std::enable_if_t<V>* = nullptr>
    operator foo<false>() {
        return {};
    }
};

运算符是一个模板,所有参数都具有默认参数。所以可以使用。但是,由于检查是在 V 上进行的,因此我们将运算符的验证延迟到重载解决为止。然后 SFINAE 将其消除。

【讨论】:

  • @VittorioRomero 的把戏不是比弹出指针更好吗?
  • @einpoklum - 定义“更好”?这是同样的伎俩。 Vittorio 的版本依赖于参数的失败,而这个版本依赖于参数的失败。在一般情况下(不是这个),一个厚脸皮的程序员可以显式地提供一个类型来绕过检查(但是没有办法使用转换运算符来做到这一点)。但是如果参数失败,攻击者就没有办法了。所以 TL;DR:在这种情况下没关系,在一般情况下,我更喜欢 SFINAE 参数。我本能地写了这篇文章。
【解决方案3】:

您可以使用辅助结构和规范来外部化您的运算符:

template <bool P> class Foo; // Forward declaration

template <bool P> struct FooConverterHelper {}; // False case: no conversion operator

template <> struct FooConverterHelper<true>
{
    operator Foo<false>();
};


template <bool P> class Foo : public FooConverterHelper<P>
{
    // Lot of stuff.
};

// Implementation of you conversion operator
FooConverterHelper<true>::operator Foo<false>()
{
    //auto* that = static_cast<Foo<true>*>(this); // If needed

    return Foo<false>{};
}

Demo

【讨论】:

  • 我喜欢它。尽管将定义传播得如此之多可能会影响可读性。 +1
  • 那是“作弊”......当然,我在另一个结构中做任何我想做的事情 - 我没有任何限制。我希望这个转换运算符用于我图书馆的用户不会注意到的静默转换。
  • @StoryTeller:另一方面,拥有struct Foo&lt;true&gt; : FooCommon&lt;true&gt; {opearator Foo&lt;false&gt;() {/**/}}; struct Foo&lt;false&gt; : FooCommon&lt;false&gt;{}; 可以避免这种情况。但它会更加“作弊”。
猜你喜欢
  • 1970-01-01
  • 2011-02-26
  • 2014-06-17
  • 2014-01-21
  • 1970-01-01
  • 1970-01-01
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多