0

I have a template class which takes on roughly the form of the given code below.

template <int index = 0>
struct Thing {
  void Hello();
  void Greet(const char *name);
};

Which works just fine for its purpose, until one decides to try and call methods on itself. Say that you define the methods as shown below.

template <>
void Thing<>::Greet(const char *name) {
  Hello();
  printf(", %s!\n", name);
}

template <>
void Thing<>::Hello() {
  printf("Hello");
}

Then calling Greet() yields an explicit specialisation error, which I find weird. The compiler ought to know all of the methods of this by its interface declaration, but for some reason it cannot resolve it in this case. This can be solved in one of two ways; either you must forward-declare any methods called on yourself, or make sure the methods are defined in an order that ensures that the ones you call are defined beforehand.

This issue is really annoying, because I have provided an interface declaration, and any specialisation of the template ought to conform to that same interface, so I really don't see why the compiler complains about this — is there any way to fix the issue without polluting the code with forward-declarations of each method, or having to order methods in a particular manner?

I hope you have some great ideas for solving the issue in a better way. Thank you in advance!

For ease of reproducing the issue, here's a snippet that will call the offending method.

int main(int argc, const char *argv[]) {
  Thing<0> thing_with_zero;
  thing_with_zero.Greet("World");
  return 0;
}
Casper B. Hansen
  • 176
  • 2
  • 10
  • 1
    Do you need **specialization**? (You don't provide generic `template void Thing::Hello()`...) – Jarod42 Sep 03 '21 at 08:25
  • Yes, yes I do. As stated this is just a simplified version of the actual class, that exhibits the same problem. The point of it is that the specialisation argument is variable for a given compile configuration, so the empty <> given are filled out by some compiler definitions. – Casper B. Hansen Sep 03 '21 at 08:29
  • The thing is that the call to `Hello();` is first seen as the generic call, but then you specialize it. Maybe specialize the whole class and provide definitions within the class would fit your needs. – Jarod42 Sep 03 '21 at 08:32
  • Would you be so kind as to provide a small example of what you mean I should change? It sounds like you have something worth trying out :) The methods must remain non-templated, for other reasons. – Casper B. Hansen Sep 03 '21 at 08:34
  • Related question [Specialization of member function template after instantiation error, and order of member functions](https://stackoverflow.com/questions/21112148)? – t.niese Sep 03 '21 at 08:38
  • @t.niese Yes, I couldn't find any related subjects before posting, but yes, that is exactly the same sort of issue! – Casper B. Hansen Sep 03 '21 at 08:49
  • The first suggestion @Jarod42 actually might be worth exploring a bit. I have it working with providing the generic as the default, and specialise when needed. So that might be the way forward. – Casper B. Hansen Sep 03 '21 at 09:01
  • 1
    `and any specialisation of the template ought to conform to that same interface`, `Thing<0>` and `Thing<1>` are two completely different types they don't need to share anything. – t.niese Sep 03 '21 at 09:06
  • "because I have provided an interface declaration, and any specialisation of the template ought to conform to that same interface" do you refer to the code you posted or to something in your actual code not included here? Asking, because in the code you posted, what t.niese said, different specializations are completely different types and dont have necessarily anything in common – 463035818_is_not_an_ai Sep 03 '21 at 09:12
  • You are correct, I am probably assuming some knowledge from the "actual" class. There are many possible values of `index`, but only one will be valid. The intent is that the base declaration is shared for each `Thing`. That is, there are no specialisations specified — just the generic one, as you see above. – Casper B. Hansen Sep 03 '21 at 09:22
  • How about `if constexpr` inside the definition to avoid specialization? – Jarod42 Sep 03 '21 at 09:24
  • Unfortunately, we don't have support for that with our compiler. But yes, that is what it is trying to achieve :) – Casper B. Hansen Sep 03 '21 at 09:32
  • For your case, regular `if` might work and let compiler does the optimization to remove dead code. – Jarod42 Sep 03 '21 at 09:47
  • You have too much faith in the compiler :D We're working on an embedded system, and the compiler is reeeally bad at optimising such things. That is the entire reason for this monstrous construction, to help the compiler :P Ideally this whole exercise could've been avoided, had the compiler done its job right :) – Casper B. Hansen Sep 03 '21 at 09:56

1 Answers1

2

You might specialize the whole class to ensure that specialization are seen:

// Primary template
// generic one
template <int index = 0>
struct Thing {
    void Hello() {/*..*/}
    void Greet(const char *name) {/*..*/}
};

// Specialization for index == 0
template <>
struct Thing<0> {
    void Greet(const char *name) {
        Hello();
        printf(", %s!\n", name);
    }
    void Hello() {
        printf("Hello");
    }
};

Alternatively, you might drop specialization with the help of if constexpr (C++17):

template <int index = 0>
struct Thing
{
    void Greet(const char *name) {
        if constexpr (index == 0) {
            Hello();
            printf(", %s!\n", name);
        } else {
            // ...
        }
    }
    void Hello() {
        if constexpr (index == 0) {
            printf("Hello");
        } else {
            // ...
        }
    }
};
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • Oooh, I see what you mean. Hm, no, unfortunately I think that breaks the ease-of-use that it is intended for. But yes, that definitely could have solved the problem, and also gave me another idea for some improvement. Good shot, thanks! – Casper B. Hansen Sep 03 '21 at 08:48
  • Not clear to me what is your "ease-of-use". You have to declare thing before using it in one way or other. – Jarod42 Sep 03 '21 at 09:14
  • Yes, but this particular thing is supposed to have many variations, so declaring ones with a shared interface is preferable :) – Casper B. Hansen Sep 03 '21 at 09:20
  • It seems to me that the problem is inherently unsolvable, given the answer in the related question. So, I'm gonna take your inputs and see if I can revise the thing in a way that can fulfil its function in a similar manner, and then mark the question as answered by this entry :) – Casper B. Hansen Sep 03 '21 at 11:09