using and overloading a template member function o

2019-06-21 02:31发布

问题:

In the following, struct Y overloads X's member function f. Both overloads are template functions, but take different arguments (typename and int), to be explicitly specified:

struct X
{
    template <typename> static bool f() { return true; }
};

struct Y : public X
{
    using X::f;
    template <int> static bool f() { return false; }
};

int main()
{
    std::cout << Y::f <void>() << " " << Y::f <0>() << std::endl;
}

This prints 1 0 using gcc, as expected. However, clang (3.3) complains that

[...] error: no matching function for call to 'f'
        std::cout << Y::f <void>() << " " << Y::f <0>() << std::endl;
                     ^~~~~~~~~~~
[...] note: candidate template ignored: invalid explicitly-specified argument
      for 1st template parameter
        template <int> static bool f() { return false; }
                                   ^

i.e., can only see Y's version. I've tried

using X::template f;

instead, with no success. The same happens for non-static (template) member functions. So is this a bug?

回答1:

This conundrum was recently explained to me in the light of another answer.

From the #clang IRC channel:

[01:16:23] <zygoloid> Xeo: this is a weird corner of the language where clang conforms but the rule is silly
[01:16:31] <Xeo> ... really? :(
[01:16:45] <zygoloid> Xeo: when deciding whether a using-declaration is hidden, we're not allowed to look at the template-parameter-list (nor the return type, iirc)
[01:17:04] <zygoloid> so the derived class declaration of operator()(T) suppresses the using-declaration
[01:17:19] <Xeo> because it has the same signature / parameter types?
[01:17:40] <zygoloid> rigth

The workaround is to not define f in the class that uses the derived version. Instead, move it into an auxiliary helper class (which, in this case begs the question, which definition you reckon should win).

  • See here for my earlier problematic case: Lambda functions as base classes

    @Xeo any idea why clang++ refuses this: coliru.stacked-crooked.com/a/6a0e6a1cac062216 (clang: coliru.stacked-crooked.com/a/5c2d6dd449a92227)

  • And here how to fix it using an extra base-class:

    @Xeo fixed it anyways, for some reason this form is not objectionable to clang++ stackoverflow.com/a/18432618/85371

Credits Thanks to @Xeo and people in the Lounge for unearthing this "silly rule"



回答2:

It comes as a huge disappointment that such a constraint exists and has not been relaxed in C++11 (there may be good reason but I cannot imagine why). I feel like it defeats the whole concept of class hierarchies.

Anyhow, here's one workaround I have found. I have included another function g that is non-static to illustrate the differences, because this case is my main interest.

template <typename Y>
struct X
{
    template <typename> static bool f() { return true; }
    template <typename>        bool g() { return true; }

    template <int I>
    static bool f() { return Y::template _f <I>(); }

    template <int I>
    bool g()
    {
        return static_cast <Y&>(*this).template _g <I>();
    }
};

class Y : public X <Y>
{
    friend class X <Y>;
    template <int> static bool _f() { return false; }
    template <int>        bool _g() { return false; }
};

int main()
{
    Y y;
    std::cout << Y::f <void>() << " " << Y::f <0>() << std::endl;
    std::cout << y. g <void>() << " " << y. g <0>() << std::endl;
}

So all overloading takes place in base class X, which implements static polymorphism by taking Y as a template argument (fortunately, this was already the case in my project so I do not change the design).

The actual Y's implementations are in private functions _f, _g. This design is good when there are many derived classes like Y with only one overload in each, and a single base class X with multiple other overloads. In this case, massive code duplication is avoided. Again, this is the case in my project.

X does not need to know the return value of these functions. Unfortunately, it does need to know the return type: I have tried e.g. auto g() -> decltype(...) and again this decltype only works in gcc. Enabling c++1y one only writes auto g() without the trailing return type specification, thus avoiding the problem with decltype. However, clang's support for "return type deduction for normal functions" (N3638) is only available in current SVN version.

Until auto g() becomes mainstream (and standard), one has to compute the return type of Y's methods by hand, which may be painful especially if there are lots of Ys.

It still looks like a mess to me, but at least not a complete one.