【问题标题】:SFINAE not working on llvm/clangSFINAE 不适用于 llvm/clang
【发布时间】:2012-06-16 16:48:11
【问题描述】:

在下面的代码中,我试图用它作为参数的任何参数调用仿函数,“whatever”是一组有限的选项(这里的两个不是我的代码中唯一的选项)。

#include <memory>
#include <iostream>

template<class T>
struct call_with_pointer {
    // last resort: T*
    template<class Callable>
    static auto call(Callable &callable, const std::shared_ptr<T> &param) -> decltype(callable(param.get())) {
        return callable(param.get());
    }
};

template<class T>
struct call_with_shared : public call_with_pointer<T> {

    // best: call with shared_ptr<T>.
    // SFINA
    // error: Candidate template ignored: substitution failure [with Callable = Test]: no matching function for call to object of type 'Test'
    template<class Callable>
    static auto call(Callable &callable, const std::shared_ptr<T> &param) -> decltype(callable(param)) {
        return callable(param);
    }

    using call_with_pointer<T>::call;
};


class Test {
public:
    bool operator () (int * x) {
        return *x == 42;
    }
};

int main ()
{
    Test t;

    auto i = std::make_shared<int>(4);

    auto x = call_with_shared<int>::call(t, i); // No matching function for call to 'call'

    return 0;
}

此代码在 VS 和 GCC 中运行良好。不幸的是,它没有叮当声。错误信息是:

调用'call'没有匹配的函数

候选模板被忽略:替换失败 [with Callable = Test]:没有匹配的函数 调用“测试”类型的对象

所以它忽略了使用智能指针的候选者。好的。但它似乎并没有继续考虑可以正常工作的继承调用。

问题:我该如何解决这个问题?我怎样才能让 llvm 在这里做正确的事情?

【问题讨论】:

  • "How can I make llvm" nitpick:这里不涉及LLVM,只是clang。
  • 是什么让您认为 clang 在这里有问题?您显然在返回类型上匹配,这是无效的。
  • 为什么call_with_pointer::call 仍然使用shared_ptr&lt;&gt; 作为第二个参数..?
  • @David 不,我没有匹配任何东西,我指望 SFINAE 消除 call_with_shared::call 并使用 call_with_pointer::call
  • @ildjarn 因为这是call_with_shared 的调用者传递的。它叫call_with_pointer 而不是called_with_pointer ;)

标签: c++ c++11 clang sfinae


【解决方案1】:

尝试以下方法:

template<class T>
struct call_with_pointer {
    // last resort: T*
    template<class Callable>
    static auto call(Callable &callable, const std::shared_ptr<T> &param) -> decltype(callable(param.get())) {
        return callable(param.get());
    }
};

template<class T>
struct call_with_pointer_2 {
    // last resort: T*
    template<class Callable>
    static auto call(Callable &callable, const std::shared_ptr<T> &param) -> decltype(callable(param)) {
        return callable(param);
    }
};

template<class T>
struct call_with_shared : public call_with_pointer<T>, public call_with_pointer_2<T>{
    using call_with_pointer<T>::call;
    using call_with_pointer_2<T>::call;
};

【讨论】:

  • 虽然解决方法所要求的,但我很想通过不编译原始代码来知道 Clang 是否有错误...
  • @ildjarn 我认为是...我会尽快提交一个错误。
  • 确保该错误尚未提交和修复。您使用的是最新版本吗?
【解决方案2】:

严格来说clang是对的,因为C++11的7.3.3p15(继承函数模板的using声明被忽略,因为它与派生类的成员函数模板具有相同的名称和参数)。尽管很明显该段落在不考虑这些不冲突的声明方面存在缺陷。

您可以通过使用 typename YieldFirstType&lt;std::shared_ptr&lt;T&gt;, Callable&gt;::type 之类的东西作为模板之一中的第二个参数类型来解决此问题。

【讨论】:

  • 对不起,我不同意。 7.3.3.p15 仅声明成员函数覆盖using 引用的继承函数,但它没有说明任何关于稍后被消除的模板化成员函数。不应考虑已消除的函数,因此不应覆盖基类中的任何内容。该注释可能更明确,但它并没有以任何方式说明它无法像 GCC 和 VS 现在处理它的方式那样工作。因此,恕我直言,GCC 和 VS 符合这一点,并且,除非您将消除的方法视为类声明的一部分,否则 clang 不是。
  • @fozi 我不明白你对措辞的解释。规范说“覆盖和/或隐藏”。在这种情况下,基函数模板被派生类函数模板隐藏。一旦找到声明,名称查找就会停止。任何可以在外部范围内找到的东西都被隐藏声明“隐藏”了。如果之后重载决议或模板参数推导失败,则游戏结束。
  • 好的,在这种情况下它将被隐藏。然后SFINAE去掉了派生类函数模板,基类模板又开始营业了。 SFINAE 不会从具有相同签名的所有基类中删除所有函数模板,它只会删除当时考虑的那个。
  • @Fozi:抱歉,没有。名字一旦隐藏,就永远隐藏。首先找到一组可能的函数。然后从这些函数中选择重载决议。如果此时重载决议失败,则编译通常会失败——编译器确实做任何进一步的搜索以在外部范围内找到比可以/将更好地工作的东西。
  • @Jerry 那么您的意思是,SFINAE 是否从所有基类中删除所有具有相同签名的函数模板?我听起来不太对劲。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-12-08
  • 2011-12-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-06
  • 1970-01-01
相关资源
最近更新 更多