Correct forward declaration of fully specialized template classes
Asked Answered
B

1

10

Assume that I have the following bunch of files:

Generic.h: Complicated template class

#pragma once

template<typename K, typename V, template<typename Key, typename Value, typename ...> typename C>
struct GenericMap
{
    C<K, V> key;
};

Special.h: Define fully specialized version of mentioned template class, simplifying the ease of use.

#pragma once

#include "Generic.h"
#include <string>
#include <map>

typedef GenericMap<std::string, int, std::map> SpecialMap;

Client.h: Client that uses SpecialMap and define forward declaration.

#pragma once

class SpecialMap; // Wrong forward declaration

struct Client {
    Client();
    SpecialMap* map;
};

Client.cpp: Clients code might know Generic.h and Special.h

#include "Client.h"
#include "Special.h"

Client::Client()
{
    map["343"] = 2;
}

main.cpp:

#include <Client.h>

int main(int argc, char**args) {
    Client c;
    return 0;
}

GenericMap represents a template class that has no forward declaration. For some users a fully specialized version SpecialMapof GenericMap should suffices, where for the ease of use a typedef is used.

Now Client uses internally SpecialMap, but the header file should only declare a forward declaration for SpecialMap.

Unfortunately, the following files will not compile. Somehow the posted forward declaration will suffice. What will be the correct one?

I'm sorry for the long listings, but it was the smallest non-working example I could think of.

Bromeosin answered 7/7, 2016 at 12:3 Comment(9)
Just an aside: I don't recommend #pragma once. Use the #ifndef... #define... #endif pattern for portability.Crore
A typedef does not declare a specialization.Pompei
Thanks for the hint. My company only uses Visual Studio. There are no portability issues. :-)Bromeosin
If a typedefdoes not declare a specialization, how can the desired behavior be accomplished correctly?Bromeosin
Well, for starters, we need to define what "specialization" actually means. "Specialization" means something very specific in C++. And, in the code posted here, I see no evidence that the template has actually been specialized, anywhere. This is probably an improper use of terminology.Pompei
I think you are right. It is more that I wanted to make GenericMap "concrete" with SpecialMap. I'm unsure about the naming.Bromeosin
@erip, do you know of a compiler that doesn't support #pragma once?Softhearted
@user2079303 Nope, but I know of many compilers that don't have to support it... all of them.Crore
@Bromeosin Using #pragma once should work on any modern compiler. Shouldn't it?Ambitendency
P
11

In the comments you clarified that you were not actually referring to C++ specialization. You were asking merely about the typedef:

typedef GenericMap<std::string, int, std::map> SpecialMap;

And that's pretty much the end of the story. This declares SpecialMap to be a typedef, a type alias. Any translation unit that needs to use SpecialMap needs to include this type definition. And only this definition. Nothing else needs to be done. It does not need to be declared in any other way. It is an alias. A search/replace of the typedef alias with its underlying type produces the same exact results. A typedef declared in one translation unit is visible only in that translation unit. There are no short-cuts for other translation units to import the typedef into their scope.

In your Client.h:

#include <Special.h>

That's where you defined this typedef, and that's the only way to pull in this definition.

However, this may also be the case where the typedef is a part of a larger header file, and it is desirable to pull in just the typedef, separately. This could be done with a header file containing only:

#include <string>
#include <map>

template<typename K, typename V,
        template<typename Key, typename Value, typename ...>
             typename C> struct GenericMap;

typedef GenericMap<std::string, int, std::map> SpecialMap;

This would be the bare minimum needed to define the typedef alias. Anything that actually needs to use it, will need to #include not just this header file, but also your Generic.h header, which actually defines the GenericMap template class, that's only forward-declared here.

Pompei answered 7/7, 2016 at 12:22 Comment(4)
Many thanks. That really solves and clarifies my problem. I'll include the posted code below in a new header file SpecialMapFwd.h.Bromeosin
I'm right now trying to figure out what I have to do when GenericMap contains the typedef typedef C<K, V> map_type. How can I possibly forward declare map_type?Bromeosin
You cannot forward declare a class member without defining the main class.Pompei
Yes. I think I have to accept this. Many thanks for your help. I really gained a lot of insights.Bromeosin

© 2022 - 2024 — McMap. All rights reserved.