C2516 在 Visual Studio 2013 中从 lambda 继承时出错

C2516 Error in inheriting from a lambda in Visual Studio 2013

#include <iostream>
#include <thread>
#include <utility>

using namespace std;

template <typename T>
class OnExitImpl : private T
{
public:
    template <typename Y>
    OnExitImpl(Y&& todo) : T(std::forward<Y>(todo)), _doIt(true) {}

    OnExitImpl(OnExitImpl&& other) : T(std::move(static_cast<T&>(other))), _doIt(other._doIt)
    {
        other._doIt = false;
    }

    ~OnExitImpl()
    {
        if (_doIt)
        {
            (*this)();
        }
    }

    void Cancel()
    {
        _doIt = false;
    }

    OnExitImpl& operator=(OnExitImpl&& other)
    {
        this->T::operator=(std::move(static_cast<T&>(other)));
        _doIt = other._doIt;
        other._doIt = false;
    }

private:
    bool _doIt;
};

template <typename T>
OnExitImpl<T> OnExit(T action)
{
    return OnExitImpl<T>(std::move(action));
}

int FetchMultithreaded(int stmt)
{
    auto onExit = OnExit([&](){ cout << stmt << endl; });

    std::thread fetchThread([&]()
    {
        auto onExit = OnExit([&](){ cout << stmt << endl; });
    });

    return 0;
}

int main()
{
    return FetchMultithreaded(0);
}

当我在 visual studio 2013 年编译时,出现以下错误:

1>Source.cpp(9): error C2516: 'T' : is not a legal base class
1>          Source.cpp(55) : see declaration of 'T'
1>          Source.cpp(55) : see reference to class template instantiation 'OnExitImpl<void (__cdecl *)(void)>' being compiled

当我在 http://ideone.com/O6I9nj 上尝试时,我只是收到关于 std::forward 的大量错误,不确定那里发生了什么。不过不确定使用的是哪个编译器。

visual studio 错误消息让我认为这是编译器错误,因为它似乎将 lambda 视为函数指针...

编辑:将模板参数添加到 std::forward 后,它现在可以在 Ideone 上编译(但由于其他原因 link 不编译),但不会为 [=29] 更改任何内容=].这让我怀疑这是一个编译器错误。

编辑 2:无论如何,我通过添加以下代码解决了这个问题:

template <typename Ret>
struct FnPtrWrapper
{
    FnPtrWrapper(Ret (*fn)(void)) : _fn(fn)
    {
        assert(fn);
    }

    Ret operator()() const
    {
        return (*_fn)();
    }

private:
    Ret (*_fn)(void);
};

template <typename Ret>
OnExitImpl<FnPtrWrapper<Ret>> OnExit(Ret(*fn)(void))
{
    return OnExitImpl<FnPtrWrapper<Ret>>(fn);
}

修复它似乎很奇怪,因为我使用的触发错误的 lambda 实际上捕获了一个函数参数,因此不应该转换为函数指针...

这是一个 VC++ 我相信以前遇到过的错误(在 SO 上)。最小复制样本:

#include <type_traits>

template <typename T>
struct B { static_assert(std::is_class<T>::value, "T not a class"); }; // (2)

int main()
{
    []{
        auto a = []{};
        static_assert( std::is_class<decltype(a)>::value, "Not a class" ); // (1)
        B<decltype(a)> b;
    }();
}

Demo on rextester's VC++.

请注意代码必须在外部 lambda 中 才会发生错误。
虽然 (1) 成功,但 b 的声明导致 (2) 失败

source_file.cpp(4) : error C2338: T not a class
source_file.cpp(11) : see reference to class template 'B<void (__cdecl *)(void)>' being compiled

这表明模板参数 decltype(a) 表示指向函数的指针类型,同时也表示 class 类型。