using and overloading a template member function of a base class?
Asked Answered
M

2

6

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?

Melaniemelanin answered 17/9, 2013 at 23:25 Comment(10)
interesting template in XDilly
Note: I tried another version where template arguments are automatically deduced, and works in both compilers (but I need explicit specification in my case).Melaniemelanin
@iavr: on another note, the way you define main() is not portable.Resinoid
@Resinoid How so? C++ compiler is required to insert return 0; by the Standard if programmer omits a return statement from main().Barocchio
@PetrBudnik: what about the args list?Resinoid
@Resinoid It can be int main() or int main(int, char**) or int main(int, char *[]). All three are legal in C++.Barocchio
Sorry about this, but my focus was above main, where the problem actually lies.Melaniemelanin
@iavr: don't sweat it, Petr is right. You're fine.Resinoid
@iavr: do you absolutely need to inherit from X?Resinoid
Yes. This is part of a much larger project: I am building tuples supporting syntax like t._() (access element, if single), t._<3> (element 3), t._<sz <3,5,2> >() (indirect tuple view referring to elements 3,5,2 of underlying tuple t) and so on. The method in question is _() and some class hierarchy is definitely needed if the code is to be elegant and maintainable.Melaniemelanin
D
6

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).

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

Don answered 17/9, 2013 at 23:43 Comment(6)
Thanks, so I guess it's bad news. Could you be a bit more specific on the workaround? Do you mean this solution involving lambdas? Because I need explicitly specified template arguments and I don't see how that fits.Melaniemelanin
@Melaniemelanin I can't possibly tell, because in your case the function templates completely shadow each other. You should probably mask a subset of overloads using SFINAE so "the other" inherited template can kick in. Be sure to make the SFINAE branches non-overlapping, or you'll end up with ambiguous calls to f or (best case)Don
There is no possible condition to check with SFINAE, as far as I can see. Both overloads are equally valid and expected to be specified by the caller.Melaniemelanin
@Melaniemelanin then, wouldn't you by definition need to disambiguate? Use different function names, add a tag template argument, the works? I'm going to head over to your self-answer now, hadn't seen it earlierDon
@Don disambiguation follows by explicitly specifying the template arguments by the caller (e.g. f <void>() vs f <0>()).Melaniemelanin
@Don Note: void is a typename, 0 is int (non-type).Melaniemelanin
M
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.

Melaniemelanin answered 18/9, 2013 at 9:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.