Home > Enterprise >  Forward declare instantiated template class
Forward declare instantiated template class

Time:08-02

We forward declare class in the api.h files, like the struct Abc in example below, because we only use std::shared_ptr<Abc> This has the advantage we can change the definition of struct Abc without recompiling api.h related files. But this forward declaration doesn't seem to work with instantiated template class, like the commented using Xyz = std::map<int, int>

Can anybody explain why it is so? and how we can forward delare something like std::map<int, int>? We have a "work-around": instead of using Xyz = std::map<int, int>, we can do class Xyz : std::map<int, int>, this is the only line we need to change. But it is strongly recommended not derive from STL. So any other suggestions?

// lib.h
#pragma once
#include <memory>
#include <map>
namespace ABC {
    struct Abc {
        int abc;
    };
    using AbcPtr = std::shared_ptr<const Abc>;
    int impl(const AbcPtr& o);

    // using Xyz = std::map<int, int>;
    // using XyzPtr = std::shared_ptr<const Xyz>;
    // int impl2(const XyzPtr& o);
}

// ------------
// lib.cpp
#include "lib.h"
int ABC::impl(const ABC::AbcPtr& o) {
    return o->abc;
}
// int ABC::impl2(const ABC::XyzPtr& o) {
    // return static_cast<int>(o->size());
// }

// ------------
// api.h
#pragma once
#include <memory>
namespace ABC {
    struct Abc;
    using AbcPtr = std::shared_ptr<const Abc>;
    // class Xyz;
    // using XyzPtr = std::shared_ptr<const Xyz>;
}

namespace Api {
    int api(const ABC::AbcPtr& o);
    // int api2(const ABC::XyzPtr& o);
}
int main();

// ------------
// api.cpp
#include "api.h"
#include "lib.h"
int Api::api(const ABC::AbcPtr& o) {
    return ABC::impl(o);
}
// int Api::api2(const ABC::XyzPtr& o) {
    // return ABC::impl2(o);
// }
int main() {
    auto obj = std::make_shared<ABC::Abc>();
    obj->abc = 100;
    int res1 = Api::api(obj);
    
    // auto obj2 = std::make_shared<ABC::Xyz>();
    // obj2->insert({ 3, 4 });
    // int res2 = Api::api2(obj2);
    return res1;
}

CodePudding user response:

But this forward declaration doesn't seem to work with instantiated template class, like the commented using Xyz = std::map<int, int>. Can anybody explain why it is so?

Because typedeffing a template does not instantiate the template. Note that when we instantiate a class template for the first time a new type like C<int> is introduced. But since a typedef-name does not introduce a new type(according to the below quoted statement), the statement using P = C<int>; does not instantiate the class template here.

Same goes for using Xyz = std::map<int, int>.

For example,

template<typename T> 
struct C 
{
};
using P = C<int>; //this does no instantiation
//Also note P is an alias for the specialization C<int> and not for the class template named `C`

In the above example, using P = C<int>; does not instantiate the class template.

This can be seen from dcl.typedef#1 which says:

A name declared with the typedef specifier becomes a typedef-name. A typedef-name names the type associated with the identifier ([dcl.decl]) or simple-template-id ([temp.pre]); a typedef-name is thus a synonym for another type. A typedef-name does not introduce a new type the way a class declaration ([class.name]) or enum declaration ([dcl.enum]) does.

(emphasis mine)

Additionally, note that P in the above example is an alias for the specialization C<int> and not for the class template named C. A class template is not a class-type by itself. Only the specialization C<int>(etc) is the class type.

CodePudding user response:

What you are trying to do is not directly possible for standard library types.

class Xyz; declares a class with the name Xyz but using Xyz = is just an alias which doesn't introduce any new type. If you declare both of them in the same scope they just conflict. The alias refers to a specialization of std::map, not to a class ABC::Xyz and an alias does not identify one with the other.

If you want to do the analogue of what you were doing with struct Abc;, you would need to forward-declare std::map instead of class Xyz;, e.g.

template<typename, typename, typename, typename> class map;

But the problem is that you would need to do that in the std namespace and you are not allowed to add declarations to the std namespace (with some exceptions that don't apply here). It causes undefined behavior.

So I think you don't really have a choice but to define your own class wrapping std::map in some way, e.g. as you suggested by inheriting from it. If you use your own types, you can just add the forward-declaration as mentioned above in the appropriate namespace.

(I do however doubt that you would need this for standard library types anyway. You have no control over their implementation. The only real advantage would be faster compilation time due to not having to include <map> in as many translation units.)

  • Related