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?

iavr
  • 7,547
  • 1
  • 18
  • 53
  • interesting template in X – 4pie0 Sep 17 '13 at 23:29
  • Note: I tried another version where template arguments are automatically deduced, and works in both compilers (but I need explicit specification in my case). – iavr Sep 17 '13 at 23:39
  • @iavr: on another note, the way you define main() is not portable. – thokra Sep 17 '13 at 23:50
  • @thokra How so? C++ compiler is required to insert `return 0;` by the Standard if programmer omits a return statement from `main()`. – lapk Sep 17 '13 at 23:55
  • @PetrBudnik: what about the args list? – thokra Sep 17 '13 at 23:56
  • @thokra It can be `int main()` or `int main(int, char**)` or `int main(int, char *[])`. All three are legal in C++. – lapk Sep 18 '13 at 00:01
  • Sorry about this, but my focus was above `main`, where the problem actually lies. – iavr Sep 18 '13 at 00:04
  • @iavr: don't sweat it, Petr is right. You're fine. – thokra Sep 18 '13 at 00:05
  • @iavr: do you absolutely need to inherit from X? – thokra Sep 18 '13 at 00:05
  • 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._ >()` (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. – iavr Sep 18 '13 at 00:13

2 Answers2

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"

Community
  • 1
  • 1
sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thanks, so I guess it's bad news. Could you be a bit more specific on the workaround? Do you mean [this solution](http://stackoverflow.com/questions/18432260/lambda-functions-as-base-classes/18432618#18432618) involving lambdas? Because I need explicitly specified template arguments and I don't see how that fits. – iavr Sep 18 '13 at 00:02
  • @iavr 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) – sehe Sep 18 '13 at 00:04
  • 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. – iavr Sep 18 '13 at 09:36
  • @iavr 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 earlier – sehe Sep 18 '13 at 09:54
  • @sehe disambiguation follows by explicitly specifying the template arguments by the caller (e.g. `f ()` vs `f <0>()`). – iavr Sep 18 '13 at 16:31
  • @sehe Note: `void` is a `typename`, `0` is `int` (non-type). – iavr Sep 18 '13 at 16:35
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.

iavr
  • 7,547
  • 1
  • 18
  • 53