不能使用基类模板成员函数

时间:2017-03-21 03:22:10

标签: c++ c++14

出于某种原因,我可以看到最顶层template<typename T> X<...>::fn(T&&),但不能看到基类版本。使用using关键字导入它们不起作用。据我了解:

  

In Class definition

     

... If the derived class already has a member with the same name, parameter list, and qualifications, the derived class member hides or overrides (doesn't conflict with) the member that is introduced from the base class.

示例代码:

#include <iostream>
#include <type_traits>

#define ENABLE_IF(...) std::enable_if_t<__VA_ARGS__, int> = 0

struct dummy {};

template<typename T>
struct always_false : std::false_type {};

template<typename...Ts>
struct X;

template<typename Tx, typename T, typename...Ts>
struct X<Tx, T, Ts...> : X<Tx, Ts...>
{
    using X<Tx, Ts...>::fn;

    template<typename R, ENABLE_IF(std::is_same<T, R>::value)>
    auto fn(R&& x)
    {
        return x;
    }
};

template<typename Tx>
struct X<Tx>
{
    template<typename R>
    auto fn(R&& x)
    {
        static_assert(always_false<R>::value, "Invalid type");
    }
};

int main()
{
    X<dummy, int, float> x;
    std::cout << x.fn(1) << std::endl;
    std::cout << x.fn(1.f) << std::endl;
    std::cout << "Hello, world!\n";
}

我在g ++,clang和VC ++上尝试了这一点,它们都有各种错误,(ambiguous callmember function disabledcould not deduce)。值得注意的是,g ++在调用X<dummy, int, float>::fn(int&&)时失败,而clang和VC ++在调用X<dummy, int, float>::fn(float&&)时失败。

据我了解,编译器在调用template<typename R> R X<dummy>::fn(R&&)时应忽略绝对基类成员函数X<dummy, int, float>::fn(float&&),因为该模板应解析为float X<dummy>::fn(float&&),这与派生成员完全匹配函数float X<dummy, float>::fn(float&&)要求在没有歧义的情况下调用派生函数。

我做错了什么?我不明白的是什么?

修改

T.C.'s answer so far来解释,“这就是规范所说的”,我想说这不是正确的解释。给出的两点相互冲突。如果它们是一个同样好的匹配(第1点),那么只有最衍生的函数签名应该是可见的(第2点)。

无论如何,如果问题是规范问题,那么如果我要禁用匹配重载的可能性会导致模糊,那么它应该消失。因此,以下应该有效:

#include <iostream>
#include <type_traits>

#define ENABLE_IF(...) std::enable_if_t<__VA_ARGS__, int> = 0


template<typename T>
struct always_false : std::false_type {};


template<typename...Ts>
struct list {};


template<typename...Ts>
struct is_one_of;

template<template <typename...> class TT, typename T, typename T1, typename...Ts>
struct is_one_of<T, TT<T1, Ts...>> : is_one_of<T, TT<Ts...>> {};

template<template <typename...> class TT, typename T, typename...Ts>
struct is_one_of<T, TT<T, Ts...>> : std::true_type {};

template<template <typename...> class TT, typename T>
struct is_one_of<T, TT<>> : std::false_type {};


template<typename...Ts>
struct X;

template<typename L, typename T, typename...Ts>
struct X<L, T, Ts...> : X<L, Ts...>
{
    using X<L, Ts...>::fn;

    template<typename R, ENABLE_IF(std::is_same<T, R>::value)>
    constexpr auto fn(R&& x) const
    {
        return x;
    }
};

template<typename L>
struct X<L>
{
    template<typename R, ENABLE_IF(!is_one_of<R, L>::value)>
    constexpr auto fn(R&& x) const
    {
        static_assert(always_false<R>::value, "Type R didn't match");
    }
};


template<typename...Ts>
struct XX : X<list<Ts...>, Ts...> {};

int main()
{
    XX<int, float> x;
    std::cout << x.fn(1) << std::endl;
    std::cout << x.fn(2.f) << std::endl;
}

它在g++下工作,但在clangVC++下遇到了同样的问题。那么,g ++是唯一一个符合这里并且其余的都有缺陷的吗?

1 个答案:

答案 0 :(得分:1)

两件事:

  1. 您的基准全能版fn与其他fn品种的匹配度相同;因此,充其量只会导致模糊的过载错误。

  2. 隐藏 using-declaration 并不考虑完整签名(函数模板将包含模板参数列表)。它only considers 1)name,2)(function)parameter-type-list,3)cv-qualification,和4) ref-qualifier (如果有的话)。如果所有四个匹配,则基类功能模板被隐藏,而不是由 using-declaration 引入。值得注意的是,不考虑模板参数列表。在您的情况下,各种fn之间唯一不同的是模板参数列表;它们都具有相同的名称,相同的参数类型列表,相同的cv资格和相同的ref-qualifier(或缺少它们)。因此,派生最多的fn将隐藏基类中的所有fn

    GCC似乎没有将此部分实现为规范,并在决定隐藏时考虑模板参数列表。

    此部分的一个可能的解决方法是将enable_if移动到一个函数参数,隐藏检查会考虑

  3. C ++中的重载解析方式是:

    1. 用于构建一组候选函数和函数模板的名称查找。
    2. 对于每个候选功能模板,执行模板参数推导。如果扣减失败,请将其从过载集中删除。如果扣减成功,则用推导的专业化替换候选功能模板。
    3. 从候选人群中消除不可行的候选人。
    4. 如果设置为空,则出错。否则,找到候选人中最好的可行功能。
    5. 隐藏在第一步操作:如果隐藏声明,则不会通过名称查找找到它,因此它不在初始候选集中,并且在任何情况下都不会被重载解析视为< / em>,无论在步骤2,3或4中发生了什么。为了解决重载问题,实际上不存在隐藏声明。

      因此,在您的情况下,基类fn都是隐藏的。这是什么意思?这意味着通过名称查找找到的唯一候选者是来自最派生类的int候选者,没有别的。如果模板参数推断和替换成功,则将调用该函数模板。如果它们失败(如在x.fn(2.f)情况下),则没有可行的候选者,并且您会收到错误。