doesn't work with a normal class. The infamous
error: field 's' has incomplete type 'S'
struct S
{
struct N
{
S s;
};
};
int main()
{
S s;
}
Though why not? Doesn't make much sense. Why then the methods of S have a complete type. And methods of N have a complete type
struct S
{
struct N
{
N(){ S s; }
};
};
int main()
{
S s;
}
and so the moment where it should work but doesn't, works with template classes
template<class=void>
struct S
{
struct N
{
S s;
};
};
int main()
{
S<> s;
}
If it works with a template it should work with a normal class. Shouldn't it?
To create an object of a class the compiler needs to know the size of the class. If the class isn't fully defined yet (which it isn't until the closing }) then the size is unknown, the compiler can't know if there's more members or not, and can thus not be able to create instance of the class.
A template isn't a class in itself, it's kind of like a blueprint for a class that will be defined sometime in the future. That's why templates can be nested, because it's only a template not an actual class. The class is known when the template is instantiated, which is it when you do e.g. S<>, and by then the compiler can use the template to create the actual class.
As noted pointers to classes works, because the size of a pointer is known.
Related
Let's assume that we have this small code:
template<typename T>
struct Test {
Test(T t) : m_t(t) {}
T m_t;
};
int main() {
Test t = 1;
}
This code easily compiles with [T=int] for Test class. Now if I write a code like this:
template<typename T>
struct Test {
Test(T t) : m_t(t) {}
T m_t;
};
struct S {
Test t = 1;
};
int main() {
S s;
}
This code fails to compile with the following error:
invalid use of template-name 'Test' without an argument list
I need to write it like Test<int> t = 1; as a class member to work. Any idea why this happens?
The reason
struct S {
Test t = 1;
};
does not work is because you aren't actually doing Test t = 1;. An in class initializer is just a convenient way to tell the compiler what value to initialize t with when one is not provided. What "actually" gets generated is
struct S {
S() : t(1) {} // created by the compiler
Test t;
};
and here you can more easily see that t isn't specified with an initializer until you call the constructor.
There is a difference between your two snippets - first Test t = 1 declares, defines, and initializes a new variable while the second only declares a member variable and specifies how it might be initialized.
The default member initializer is relevant only in the context of a constructor without t in its member initializer list and there can easily be multiple constructors, each initializing t in different way.
The following is valid C++, what should type of t be deduced to?
struct S {
Test t = 1;
S(){}
S(int):t(1){}
S(double):t(true){}
};
If this were to be supported, you hit the implementation issue of making type/size/layout of the class dependent on the definition of constructors which are likely in different translation units. Therefore it would make it impossible to define include classes such as S (if one moved the definitions to some .cpp) via header files.
I've been struggeling with the following problem:
// this is in a header file
template <typename T>
struct Foo {
T x, y;
// ... other stuff
struct Bar {
int a, b;
// ... other stuff
void f() const;
};
Bar h() const
{ return { reinterpret_cast<int>(this->x), reinterpret_cast<int>(this->y) }; }
};
It's clear that Foo::h() needs to be implemented in the header file since it depends on the template argument.
But this is not the case for Foo::Bar::f().
I would like to implement this in a separate .cpp-file, since it only needs to compile once and so on.
Just as a note: I would like to keep this as a nested type for namespacing reasons.
Is there a nice way to do this ?
I dont't see why this shouldn't work, since Foo::Bar does not depend on the template argument at all.
Thank you very much !
Edit: fixed typo
I dont't see why this shouldn't work, since Foo::Bar does not depend on the template argument at all
This is not correct conclusion - the nested class has access to all names (private, protected, etc) to which the enclosing class has access, so depending on how the enclosing class is instantiated, the nested class has different surrounding context, thus Foo<int>::Bar and Foo<char>::Bar are not the same classes (to be precise - the nested class is part of the enclosing class definition, so without Foo<int>/Foo<char>, Bar doesn't exist, but since these are different classes, Bar under those classes are also different)
not sure what you mean by "namespacing reasons", but if you just want to access it like Foo<T>::Bar, you can use alias.
struct ChocolateBar {
int a, b;
// ... other stuff
void f() const;
};
template <typename T>
struct Foo {
T x, y;
// ...
using Bar = ::ChocolateBar;
Bar h() const { return { reinterpret_cast<int>(this->x), reinterpret_cast<int>(this->y) }; }
};
I want to add a static function to a template class that is accessible without passing template parameters first. Is that possible?
namespace foo {
template <typename T>
class bar {
public:
static void eggs();
};
}
foo::bar<some_t>::eggs(); // works
foo::bar::eggs(); // does not work
I would like to avoid moving eggs() to the foo namespace or to create a new namespace for it (eg. foo::bar_::eggs(), ugh).
No. That is not how template classes work. What you want to do is not possible in C++.
Remember that foo::bar does not name any type, but solely a template that can be used to create other types.
Besides using typedefs/type aliases (through using), you can perhaps have a non-templated base class for you templates, and then put your static members there. If you use public inheritance, changing the static member in any of the templated classes will change in all of them.
After experimenting with your code:
I want to add a static function to a template class that is accessible
without passing template parameters first. Is that possible?
namespace foo {
template <typename T>
class bar {
public:
static void eggs();
};
}
foo::bar<some_t>::eggs(); // works
foo::bar::eggs(); // does not work
I would like to avoid moving eggs() to the foo namespace or to create
a new namespace for it (eg. foo::bar_::eggs(), ugh).
I have come to the conclusion that, the first instance of
foo::bar<some_t>::eggs(); // works while
foo::bar::eggs(); // doesn't
Is due to the fact that when working with templates, anything within the class has to be relative to a specific object, even if you do not want the function to be. I even tried using function pointers and tried to save them to template class and without no avail I couldn't even get that to compile. I do not see much of an option for you in this situation. There maybe other tricks out there that someone might know, but not from what I can see.
You can make the template parameter optional and you can define a specialized template. Like this:
namespace foo {
template <typename T = void> class bar {
public:
static void eggs() { cout << "First there was the egg" << endl; }
};
template <> class bar<void> {
public:
static void eggs() {
cout << "Then there was the chicken... or was it?" << endl;
}
};
}
auto main() -> int {
foo::bar<int>::eggs(); // egg
foo::bar<>::eggs(); // chicken
return 0;
}
second template question of the day, what a n00b:
I have a template class:
template <class T>
class foo{
private:
//...
T SubFoo;
//...
};
I also have a class called myClass. I would like to have objects of the kind:
foo<myClass> myObject;
But, and here's the problem, I would like to be able to get a pointer to myObject from myObject.SubFoo. That means that one of the members of the class myClass should be an instantiation of the template class foo.
So I can do:
class myClass{
//...
foo<myClass>* point2myClass;
}
However, it seems that this does not work because
./foo.h:103: error: ‘foo::SubFoo’ has incomplete type
When defining myClass, the program finds the line
foo<myClass>* point2myClass;
It goes to the defintion of foo and it finds:
T SubFoo;
but T, in this case myClass, has not yet been defined (that is what the program was doing!), so it doesn't know what T is, hence the error.
If I interchange the order of declarations, it will also fail because "foo" will not be defined.
How can I make this work??
Thanks a million!
The following code, should definitely work just fine. If your code is different, please specify where.
template < typename T >
struct A
{
T x;
};
struct X
{
A<X>* x;
};
int main()
{
X a;
}
This is a strange question because I already know the 'coding' answer. I just want to get a better understanding of why it is so. There are guru's here who have a knack of explaining these things better than the C++ standard :)
Below we have a means to define an abstract factory template that allocates objects based on a string as a key (it is a contrived example):-
#include <iostream>
#include <map>
#include <string>
using namespace std;
template <typename T, typename TProduct>
TProduct *MyFactoryConstructHelper(const T *t)
{
if (!t) return new T;
return new T(*static_cast<const T*>(t));
}
template <typename TProduct>
class AbstractFactory
{
public:
typedef TProduct *(*MyFactoryConstructor)(const void *);
typedef map<string, MyFactoryConstructor> MyFactoryConstructorMap;
static TProduct *Create(const string &iName)
{
MyFactoryConstructor ctr = mTypes[iName];
TProduct *result = NULL;
if(ctr) result = ctr(NULL);
return result;
}
template <typename T>
static bool Register(const string &iName) {
typedef TProduct*(*ConstructPtr)(const T*);
ConstructPtr cPtr = MyFactoryConstructHelper<T, TProduct>;
string name = iName;
mTypes.insert(pair<string,MyFactoryConstructor>(name, reinterpret_cast<MyFactoryConstructor>(cPtr)));
return(true);
}
protected:
AbstractFactory() {}
static MyFactoryConstructorMap mTypes;
};
template <typename TProduct>
map<string, /*typename*/ AbstractFactory<TProduct>::MyFactoryConstructor> AbstractFactory<TProduct>::mTypes;
Here is an example of how we use it: -
class MyProduct
{
public:
virtual ~MyProduct() {}
virtual void Iam() = 0;
};
class MyProductFactory : public AbstractFactory<MyProduct>
{
public:
};
class ProductA : public MyProduct
{
public:
void Iam() { cout << "ProductA" << endl; }
};
class ProductB : public MyProduct
{
public:
void Iam() { cout << "ProductB" << endl; }
};
int _tmain(int argc, _TCHAR* argv[])
{
MyProduct *prd;
MyProductFactory::Register<ProductA>("A");
MyProductFactory::Register<ProductB>("B");
prd = MyProductFactory::Create("A");
prd->Iam();
delete prd;
prd = MyProductFactory::Create("B");
prd->Iam();
delete prd;
return 0;
}
It will not compile as is, complaining that the map does not have a valid template type argument for the data type. But if you remove the comments around the 'typename' keyword in the static member definition, everything compiles and works fine... why?
and also, can I make this any better? :)
The standard tries to allow an implementation to parse and
detect as many errors in a template as possible when it reads
the template definition, before any instantiations. C++ is not
context independent, however, and it's very difficult, if not
impossible, to correctly parse statements if you don't know
which symbols are types and which are templates. If the symbol
is dependent (depends on the template parameters in some way),
you have to tell the compiler when it is a type or a template;
otherwise, the compiler must assume that it is something else.
In this case, you're telling the compiler that
AbstractFactory::MyFactoryConstructor names a type,
and not something else.
If, when the template is instantiated, and the compiler can see
to what the symbol is really bound, it turns out that you lied
(e.g. AbstractFactory::MyFactoryConstructor is in fact
an int), then the compiler will get mad at you.
Note too that the fact that the AbstractFactory was defined
before the definition requiring the typedef doesn't change
anything. There could always be an explicit specialization for
the type you're instantiating AbstractFactory on.
The simple reason is that even though you and I looking at the code know that AbstractFactory::MyFactoryConstructor is a type, the compiler doesn't -- or rather, is prohibited by the standard from knowing this. As far as it knows in the first pass of compilation, MyFactoryConstructor -- itself inside a template yet to be fully realized -- could be something else, like a static variable, which isn't allowed as the second template argument to the map, which requires a type. Supplying "typename" permits the compiler to treat it as a type as soon as it is first encountered.