【问题标题】:"What happened to my SFINAE" redux: conditional template class members?“我的 SFINAE 怎么了”redux:条件模板类成员?
【发布时间】:2012-07-17 22:58:00
【问题描述】:

我是编写模板元编程代码的新手(而不是仅仅阅读它)。所以我遇到了一些菜鸟问题。这篇名为 "What happened to my SFINAE?" 的非 SO 帖子很好地总结了其中一个,我将 C++11 化为:

(注意:我给这些方法提供了不同的名称,只是为了帮助我在这个“思想实验”示例中进行错误诊断。请参阅@R.MartinhoFernandes's notes,了解为什么您实际上不会在实践中为非重载选择这种方法.)

#include <type_traits>

using namespace std;

template <typename T>
struct Foo {
    typename enable_if<is_pointer<T>::value, void>::type
    valid_if_pointer(T) const { }

    typename disable_if<is_pointer<T>::value, void>::type
    valid_if_not_pointer(T) const { }
};

int main(int argc, char * argv[])
{
    int someInt = 1020;
    Foo<int*>().valid_if_pointer(&someInt);    
    Foo<int>().valid_if_not_pointer(304);

    return 0;
}

@Alf 说 SFINAE 发生的事情是“它一开始就不存在”,并给出了编译的建议,但模板化的是函数而不是类。这在某些情况下可能是正确的,但并非全部。 (例如:我正在专门尝试编写一个容器,该容器可以容纳可能是也可能不是可复制构造的类型,我需要基于此来打开和关闭方法。)

作为一种解决方法,我试了一下……它似乎可以正常工作。

#include <type_traits>

using namespace std;

template <typename T>
struct FooPointerBase {
    void valid_if_pointer(T) const { }
};

template <typename T>
struct FooNonPointerBase {
    void valid_if_not_pointer(T) const { }
};

template <typename T>
struct Foo : public conditional<
    is_pointer<T>::value, 
    FooPointerBase<T>,
    FooNonPointerBase<T> >::type {
};

int main(int argc, char * argv[])
{
    int someInt = 1020;
#if DEMONSTRATE_ERROR_CASES
    Foo<int*>().valid_if_not_pointer(&someInt);
    Foo<int>().valid_if_pointer(304);
#else
    Foo<int*>().valid_if_pointer(&someInt);
    Foo<int>().valid_if_not_pointer(304);
#endif
    return 0;
}

但是如果这没有被破坏(是吗?),它肯定没有遵循一个好的通用方法来基于嗅探特征类型来打开和关闭模板化类中的方法。有没有更好的解决方案?

【问题讨论】:

  • "这可能适用于某些情况,但并非全部。"老实说,我能想到的唯一情况是你想要一个稳定的二进制接口。无论如何,不​​,我认为没有更好的解决方案。
  • @R.MartinhoFernandes 我很困惑......你认为嗅探其包含类型的类型特征的容器几乎没有适用性,并且基于嗅探打开和关闭不同的方法? (我会引用 boost::optional 应用于可移动类型,因为我正在研究这个和一些相关问题。) 你是说你认为没有比我更好的解决方案想出了,或者您认为没有比 Alf 的去模板化类型更好的解决方案? :-/
  • @HostileFork 我认为没有比模板化功能或您的功能更好的解决方案了。两者都很好,我更喜欢对函数进行模板化(就像下面的 Flexo 所做的那样),因为一般来说,它不太麻烦(什么?一对新类针对每个标准?)。是的,您的用例非常好。我只是认为除非您提供两个重载,否则不需要 SFINAE。实际上,我认为 SFINAE 更糟糕。给我一点时间,我认为这更适合答案;)

标签: c++ c++11 template-meta-programming sfinae typetraits


【解决方案1】:

首先,C++11 did not carry forward boost's disable_if。因此,如果您要转换 boost 代码,则需要使用带有否定条件的 enable_if(或重新定义自己的 disable_if 构造)

其次,要让 SFINAE 进入并应用于方法级别,这些方法本身必须是模板。然而,您的测试必须针对这些模板的参数进行......所以像enable_if&lt;is_pointer&lt;T&gt; 这样的代码将不起作用。你可以通过让一些模板参数(比如 X)默认等于 T 来解决这个问题,然后抛出一个静态断言,表明调用者没有明确地将它专门化为其他东西。

这意味着不要写:

template <typename T>
struct Foo {
    typename enable_if<is_pointer<T>::value, void>::type
    valid_if_pointer(T) const { /* ... */ }

    typename disable_if<is_pointer<T>::value, void>::type
    valid_if_not_pointer(T) const { /* ... */ }
};

...你会写:

template <typename T>
struct Foo {
    template <typename X=T>
    typename enable_if<is_pointer<X>::value, void>::type
    valid_if_pointer(T) const {
        static_assert(is_same<X,T>::value, "can't explicitly specialize");
        /* ... */
    }

    template <typename X=T>    
    typename enable_if<not is_pointer<X>::value, void>::type
    valid_if_not_pointer(T) const {
        static_assert(is_same<X,T>::value, "can't explicitly specialize");
        /* ... */
    }
};

现在两者都是模板,enable_if 使用模板参数 X,而不是整个类的 T。这特别是关于在为重载解析创建候选集时发生的替换——在您的初始版本中,重载解析期间没有发生模板替换。

请注意,静态断言是为了保留原始问题的意图,并防止有人能够编译如下内容:

Foo<int>().valid_if_pointer<int*>(someInt);

【讨论】:

  • 我确实尝试过……(我制作了模板并添加了虚拟参数,我没有想到 X=T 技巧)。但是我遇到了no type named ‘type’ in ‘struct std::enable_if&lt;false, void&gt; 的错误(gcc 4.7),使用你的代码我得到了同样的东西。这真的适合你吗?
  • 这个坏了。 X 需要在非推断上下文中,否则它可能与 T 不同。例如,您的代码接受Foo&lt;int*&gt; x; x.valid_if_not_pointer(0);
  • @JohannesSchaub-litb 但是如果有人担心类型不匹配,那么检查类型是否相同不是真正的答案吗?喜欢enable_if&lt; is_same&lt;X, T&gt;::value and is_pointer&lt;X&gt;::value, void&gt;::type?通过测试,使用 X 或 T 无关紧要。没有它,您可能会遇到最终测试虚假类型的情况...like Foo&lt;int&gt;().valid_if_pointer&lt;int*&gt;(someInt);
  • 其实在方法体中加一个static_assert( is_same&lt;X, T&gt;::value, "cannot explicitly specialize this method" );可能更合适。
  • @HostileFork - 编辑看起来不错,我绝对不介意对其进行编辑 - 这就是网站的重点!我希望更多的人能够进行更多的编辑,而不是亲自进行风格的问答。
【解决方案2】:

在我看来,你不希望 SFINAE 出现在这里。 SFINAE 有助于在不同的模板化重载之间进行选择。基本上,您使用它来帮助编译器在template &lt;typename Pointer&gt; void f(Pointer);template &lt;typename NotPointer&gt; void f(NotPointer); 之间进行选择。

这不是你想要的。在这里,您有两个名称不同的函数,而不是两个相同的重载。编译器已经可以在template &lt;typename Pointer&gt; void f(Pointer);template &lt;typename NotPointer&gt; void g(NotPointer); 之间进行选择。

我将举一个例子来解释为什么我认为 SFINAE 在这里不仅没有必要,而且不受欢迎。

Foo<int> not_pointer;
Foo<int*> pointer;

not_pointer.valid_if_pointer(); // #1
not_pointer.valid_if_not_pointer(); // #2
pointer.valid_if_pointer(); // #3
pointer.valid_if_not_pointer(); // #4

现在,假设您设法与 SFINAE 一起工作。尝试编译这段代码将在第 1 行和第 4 行产生错误。这些错误将类似于“找不到成员”或类似的内容。它甚至可能将该函数列为重载决议中的废弃候选函数。

现在,假设您没有使用 SFINAE 执行此操作,而是使用 static_assert。像这样:

template <typename T>
struct Foo {
    void valid_if_pointer(T) const {
        static_assert(std::is_pointer<T>::value, "valid_if_pointer only works for pointers");
        // blah blah implementation
    }

    void valid_if_not_pointer(T) const {
        static_assert(!std::is_pointer<T>::value, "valid_if_not_pointer only works for non-pointers");
        // blah blah implementation
    }
};

这样你会在同一行得到错误。但是您会得到非常短且有用的错误。多年来,人们一直在向编译器作者提出一些问题。它现在就在你家门口:)

您会得到相同的结果:两种情况都会出错,但没有 SFINAE 会得到更好的结果。

另外请注意,如果您根本没有使用static_assert,并且函数的实现仅在分别给定指针或非指针时才有效,您仍然会在相应的行上出现错误,除非可能更糟糕那些。

TL;DR:除非您有两个实际的模板函数同名,否则最好使用static_assert 而不是 SFINAE。

【讨论】:

  • 感谢您对应用程序的批评,我知道那部分。我出于测试目的更改了名称,以便在调试机制时消除混淆。事实证明,我尝试过的替代方案的唯一问题是 C++11 中没有 disable_if,我误以为我在尝试时遇到的错误是 SFINAE 问题,而实际上它们是“没有这样的”像 disable_if" 的问题!但我很高兴学会了 template&lt;typename X=T&gt; 技巧,这似乎是正确的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-08
  • 1970-01-01
  • 2020-10-10
  • 2020-10-03
相关资源
最近更新 更多