功能模板专门化失败:编码错误或MSVC2013错误?

时间:2015-08-31 13:42:20

标签: c++ templates visual-c++

以下代码使用g ++ 4.8.1(mingw)以及http://gcc.godbolt.org/上的各种最新clang和gcc版本正确编译,但是对于MSVC2013 Update 4,它失败了,显然是由于typedef typename A<T>::value_type value_type;线。编译器给出以下错误:

  

x.cpp(30):错误C2893:无法专门化功能模板“void B<C,int>::bar(void)”        使用以下模板参数:        'MemberFn=void C::baz(int)'

更简单的typedef typedef T value_type;可以使用。

我做错了吗?或者这是Microsoft C ++编译器中的已知错误?

补充问题:

  • 从风格的角度来看,假设我有选择权,那么在派生类中重新定义类型(例如typedef T value_type;)或从基类中引入它们会更加惯用(例如{{ 1}},或者在C ++ 11中typedef typename A<T>::value_type value_type;)? (注意:我现在正在对C ++ 03的兼容性进行套期保值,因此避免使用using typename A<T>::value_type;。)这里有一些不确定的讨论:Use typedef/using from templated base class in derived class我问的原因是{{1}无论如何,我是首选,我不需要太担心。
using

更新#1:这是来自框架的简化测试用例。我不是要求对结构进行一般批评。如果没有上下文,我不希望这个结构有意义。

更新#2:以下是一个相关问题,讨论typedef T value_type;#include <cstdio> template <typename T> struct A { typedef A<T> base_type; typedef T value_type; }; template <typename Derived, typename T> struct B : public A<T> { typedef Derived derived_type; //typedef T value_type; // this works typedef typename A<T>::value_type value_type; // this fails in MSVC 2013 //using typename A<T>::value_type; // this fails in MSVC 2013 too template<void (derived_type::*MemberFn)(value_type) > void bar() { (static_cast<derived_type*>(this)->*MemberFn)(42); } }; struct C : public B<C, int> { void baz(int i) { std::printf("baz(%d)\n", i); } void foo() { bar<&C::baz>(); } }; int main(int, char *[]) { C c; c.foo(); } 结合使用是否有效:C++ template inheritance issue with base types

更新#3:我已在Microsoft Connect上提交了公开错误报告。如果您可以重现该问题,并认为它是一个错误,请提出错误信息:https://connect.microsoft.com/VisualStudio/feedback/details/1740423

2 个答案:

答案 0 :(得分:1)

看起来正确。我用VS2015测试了它,同样的错误信息。实际上,它应该在没有B中的typedef的情况下工作,因为它从A继承了'value_type',所以如果是struct B,它也在命名空间中。

答案 1 :(得分:1)

我有一个可能的解决方案(我只能用VS2015进行测试)

如果您使用基本类型作为模板参数本身,它将被解析,您可以访问value_type

template <typename T>
struct A {
    typedef T value_type;
    typedef A<T> base_type;
};

template <typename Derived, typename T, typename Base = A<T>>
struct B : public Base
{
    typedef Derived derived_type;
    typedef void (derived_type::*member_func_type)(typename Base::value_type);

    template<member_func_type MemberFn>
    void bar()
    {
        (static_cast<derived_type*>(this)->*MemberFn)(42);
    }
};


struct C : public B<C, int> 
{
    void baz(int i)
    {
        std::printf("baz(%d)\n", i);
    }

    void foo()
    {
        bar<&C::baz>();
    }
};

或者我更喜欢的解决方案:

template <typename T>
struct A {
    typedef A<T>    base_type;
    typedef T       value_type;
};

template <typename Derived, typename T, typename Base = A<T>>
struct B : public Base
{
    typedef Derived                     derived_type;
    typedef typename Base::base_type    base_type;
    typedef typename Base::value_type   value_type;

    typedef void (derived_type::*member_func_type)(value_type);

    template<member_func_type MemberFn>
    void bar()
    {
        (static_cast<derived_type*>(this)->*MemberFn)(42);
    }
};

我必须承认这个解决方案有它的缺点,比如用其他东西覆盖第3个模板参数的能力。至少其他基类需要声明base_typevalue_type

修改 使用static_assert可能会阻止更改Base模板参数。

template <typename Derived, typename T, typename Base = A<T> >
struct B : public Base
{
    static_assert(std::is_same<Base, typename A<T>>::value, "Redefinition of template parameter Base is not allowed");

    typedef Derived                     derived_type;
    typedef typename Base::base_type    base_type;
    typedef typename Base::value_type   value_type;

    typedef void (derived_type::*member_func_type)(value_type);

    template<member_func_type MemberFn>
    void bar()
    {
        (static_cast<derived_type*>(this)->*MemberFn)(42);
    }
};

示例:

template <typename T>
struct D {
    typedef D<T>    base_type;
    typedef T       value_type;
};

struct E : public B<C, int, D<int>>
{
};

结果:

error C2338: Redefinition of template parameter Base is not allowed

<强>更新

更改B的模板参数的顺序会更改行为。这里原始代码只有TDerived的顺序发生了变化。

#include <cstdio>

template <typename T>
struct A {
    typedef A<T> base_type;
    typedef T value_type;
};

template <typename T, typename Derived>
struct B : public A<T> {
    typedef Derived derived_type;
    //typedef T value_type; // this works
    typedef typename A<T>::value_type value_type; // this fails in MSVC 2013
                                                  //using typename A<T>::value_type; // this fails in MSVC 2013 too

    template<void (derived_type::*MemberFn)(value_type) >
    void bar()
    {
        (static_cast<derived_type*>(this)->*MemberFn)(42);
    }
};

struct C : public B<int, C> {
    void baz(int i)
    {
        std::printf("baz(%d)\n", i);
    }

    void foo()
    {
        bar<&C::baz>();
    }
};


int main(int, char *[])
{
    C c;
    c.foo();
}

这编译并正常工作。

我还不完全确定这是一个错误。

<强>更新

所以这似乎是MSVC中缺少的功能。 MSVC(截至2015 / 14.0)似乎不支持&#34;两阶段名称查找&#34;。

  

Darran Rowe :VC还没有实现三个C ++ 98/03功能:两阶段   名称查找,动态异常规范和导出。两相   名称查找在2015年仍未实现,但它在编译器上   团队要做的事情列表,等待代码库现代化。动态   异常规范也未实现(VC给出   抛出()并忽略其他形式的非标准语义,但它们   在C ++ 11中被弃用了,现在没有人关心它们了   noexcept。我们不太可能实施它们,而且还有   甚至一直在谈论从C ++中删除它们17。最后,出口是   在C ++ 11中删除。

来源C++11/14/17 Features In VS 2015 RTM

2012年有一个错误请求该功能,但在没有评论的情况下关闭了该功能:support two-phase name lookup - by Ivan Sorokin

所以看起来你在这里正在做的一切,但是MSVC并不支持这部分C ++标准。