重载之前模板实例化出错

时间:2017-10-11 14:05:49

标签: c++ c++11 templates overloading template-instantiation

给出以下代码

#include <type_traits>
#include <utility>

template <typename T>
class Something {
public:
    template <typename F>
    auto foo(F&&)
        -> decltype(std::declval<F>()(std::declval<T&>())) {}
    template <typename F>
    auto foo(F&&) const
        -> decltype(std::declval<F>()(std::declval<const T&>())) {}
};

int main() {
    auto something = Something<int>{};
    something.foo([](auto& val) {
        ++val;
    });
}

https://wandbox.org/permlink/j24Pe9qOXV0oHcA8

当我尝试编译这个时,我得到错误,说我不允许在main中修改lambda中的const值。这意味着模板在某种程度上都在类中实例化,这导致了一个硬错误,因为错误发生在lambda的主体中。

有什么规则?为什么重载决策尝试实例化永远不会被调用的模板?永远不应该在这里调用const,那么它为什么要尝试完全实例化呢?

然而,奇怪的是,当我将定义更改为decltype(auto)返回并添加代码以执行与尾随返回类型建议相同的操作时,我看不到错误。表明模板没有完全实例化?

template <typename F>
decltype(auto) foo(F&& f) {
    auto t = T{};
    f(t);
}
template <typename F>
decltype(auto) foo(F&& f) const {
    const auto t = T{};
    f(t);
}

我想编译器在使用传递的函数至少实例化签名之前不知道要调用哪个函数。但这并不能解释为什么decltype(auto)版本有效......

2 个答案:

答案 0 :(得分:4)

(对于缺乏正确的标准术语而道歉,正在努力......)

当调用something.foo时,必须考虑所有可能的重载:

template <typename F>
auto foo(F&&)
    -> decltype(std::declval<F>()(std::declval<T&>())) {}

template <typename F>
auto foo(F&&) const
    -> decltype(std::declval<F>()(std::declval<const T&>())) {}

为了检查过载是否可行,编译器需要评估尾随decltype(...)。第一个decltype将被评估且没有错误,它将评估为void

第二个会导致错误,因为你试图用const T&调用lambda。

由于lambda不受约束,因此在lambda体的实例化期间会发生错误。发生这种情况是因为(默认情况下)lambda使用自动返回类型推导,这需要实例化lambda的主体。

因此,不可行的重载将导致编译错误而不是SFINAEd。如果你将lambda约束如下......

something.foo([](auto& val) -> decltype(++val, void()) {
    ++val;
});

...不会发生错误,因为通过SFINAE认为过载是不可行的。此外,您将能够检测lambda调用是否对特定类型有效(即T支持operator++()?)来自Something::foo

当您将返回类型更改为decltype(auto)时,将从函数正文中推断出返回类型。

template <typename F>
decltype(auto) foo(F&& f) {
    auto t = T{};
    f(t);
}

template <typename F>
decltype(auto) foo(F&& f) const {
    const auto t = T{};
    f(t);
}

由于您的something实例不是const,因此此处将采用非const限定的重载。如果您的main定义如下:

int main() {
    const auto something = Something<int>{};
    something.foo([](auto& val) {
        ++val;
    });
}

即使使用decltype(auto),您也会收到相同的错误。

答案 1 :(得分:1)

实际上,我认为问题的关键在于

  

只有函数类型的直接上下文中的无效类型和表达式及其模板参数类型才会导致扣减失败。 [注意:替换为类型和表达式可能会导致诸如类模板特化和/或函数模板特化的实例化,隐式定义函数的生成等效果。此类效果不在   “直接上下文”和可能导致程序格式不正确。 - 结束说明]

所以,问题是,如果在推断其回归类型的过程中触发的lambda实例化是在其直接上下文中考虑的吗?

例如,如果lambda返回类型是显式的:

something.foo([](auto& val) -> void {
    ++val;
});

代码编译(没有sfinae,它只是非const是最佳匹配)。

但是,OP的lambda具有自动返回类型推导,因此lambda被实例化并且上述规则适用。