What's the relationship between CStringEx, CTmplStringBase and CTmplStringBaseTmpl again?
Printable View
What's the relationship between CStringEx, CTmplStringBase and CTmplStringBaseTmpl again?
Oh yeah, sorry about that >_<
CStringEx is the typedef name for the actual class named CTmplStringBase:
And the rest are macros:Code:typedef CTmplStringBase< char, StrTraits<char> > CStringExA;
typedef CTmplStringBase< wchar_t, StrTraits<wchar_t> > CStringExW;
typedef CTmplStringBase< TCHAR, StrTraits<TCHAR> > CStringEx;
To get rid of the long nasty template lists.Code:# define CTmplStringBaseTmpl Strings::CTmplStringBase<T, Traits>
# define CTmplStringBaseTemplate template<typename T, typename Traits>
You do realize that CTmplStringBaseTemplate is not significantly shorter than template<typename T, typename Traits>, but several magnitudes less readable, right?
This is indeed weird.
Perhaps, but it helps me a lot since I don't have to go to 100 places and change the code whenever it changes. Plus it's just one of many, sometimes very long.
But I also tried this. "The simplest possible snippet of code that compiles".
Will also give error about unable to deduce the template T.Code:template<typename T> class CTest { public: CTest() {} CTest(const wchar_t*) {} };
template<typename T> void operator += (CTest<T>&, const CTest<T>&) {}
void Help()
{
CTest<wchar_t> a;
a += L"test";
}
If I remove the template and explicitly specify wchar_t, it compiles.
If I explicitly specify the constructor, such as a += CTest<wchar_t>(L"test"), it works too, but not with the templates.
Well, GCC agrees with VC++, even though it's even less informative. Simplest solution is apparently to just make += a member function. Or overload the free operator += for const wchar_t*.
Hm. Weird.
Either a member or an overload for each of the types it can accept...
Yep, you are trying to make the compiler guess when it can't.Quote:
I must be doing something wrong here...
You'd better hope that you don't need to extend a series.Quote:
To get rid of the long nasty template lists.
It isn't weird, but it is annoying.Quote:
This is indeed weird.
Also, 'operator +=' should really be a method.
Anyway, the reason this can't work: any specialization or even instantiation of 'CTmplStringBase' may have a constructor that takes a 'const wchar_t *' as an argument. How would the constructor choose between these instantiations?
You have a few options (for this and the other operators):
1): Create a less ambiguous interface that forwards to the correct implementation--by explicitly instantiating the template function.
2): Create the function as a method, losing "left hand conversion".
3): Get partial "left hand" conversion by declaring the function as a friend and implementing it "inline"--not 'inline'.
4): My way, or less arrogantly: the way of the inspiring Andrei Alexandrescu. (Note: You lose implicit 'CTmplStringBase<X>' to 'CTmplStringBase<Y>' conversion, but this too can be overcome by adding in a little '1'.)
Soma
Code:template<typename T> class CTest { public: CTest() {} CTest(const wchar_t*) {} typedef CTest<T> this_type;};
template<typename T> void operator += (CTest<T>&, const typename CTest<T>::this_type &){}
void Help()
{
CTest<wchar_t> a;
a += L"test";
}
I knew there was such a thing as suppressing argument detection from the second parameter, but I couldn't remember how. My attempt 'const CTest<typename identity<T>::type>&' failed.Quote:
the way of the inspiring Andrei Alexandrescu.
Sorry. That was just poor wording. You obviously keep conversion; what you don't keep is type information.Quote:
(Note: You lose implicit 'CTmplStringBase<X>' to 'CTmplStringBase<Y>' conversion, but this too can be overcome by adding in a little '1'.)
*shrug*
Soma
Apparently, that's the only I can figure.
But I would assume the compiler should be able to guess, even in this instance.
Maybe you're right about that. I'll move it inside the class, but there are more free operators that would break, and besides, it's just a test. Yesssss...Quote:
Also, 'operator +=' should really be a method.
It would seem to me that if that is the case, then it would be ambiguous, but not when there is only one instance and no specializations.Quote:
Anyway, the reason this can't work: any specialization or even instantiation of 'CTmplStringBase' may have a constructor that takes a 'const wchar_t *' as an argument. How would the constructor choose between these instantiations?
The way I see it:
The compiler sees that it wants a class CTest of type X.
Therefore the compiler looks at the class for CTest.
It looks for a constructor to take one argument.
It finds a constructor that takes const T*. Seeing as I add a wchar_t*, the compiler can easily deduce the type of T to wchar_t and call the constructor.
The temporary object will then be of type CTest<wchar_t> and the compiler can deduce the type of T for the operator.
But apparently my analogy is incorrect.
More complexity and tricks to put in the book :DQuote:
4): My way, or less arrogantly: the way of the inspiring Andrei Alexandrescu. (Note: You lose implicit 'CTmplStringBase<X>' to 'CTmplStringBase<Y>' conversion, but this too can be overcome by adding in a little '1'.)
O_o
A particular template is "compiled" when the definition is encountered and only "instantiated" later. A specialization can be crafted after the specialization is used. The source isn't ambiguous because the compiler can't even determine where to start looking for ambiguities; the compiler knows that an unlimited number of cases may yet exist that would yield a match. How would the compiler determine what types a particular template class should be instantiated with in order to examine the interface for a particular constructor?Quote:
It would seem to me that if that is the case, then it would be ambiguous, but not when there is only one instance and no specializations.
No, the compiler only sees that the "left hand" argument is a 'CTest<X>'.Quote:
The compiler sees that it wants a class CTest of type X.
I think this is your biggest problem. There is no class 'CTest'. There is a class named 'CTest<X>', a class named 'CTest<Y>', and an infinite number of other classes named 'CTest<?>'.Quote:
Therefore the compiler looks at the class for CTest.
Where? The compiler can't look at 'CTest'. It can only look at 'CTest<?>' for any given '?'. How would the compiler know which 'CTest<?>' to examine?Quote:
It looks for a constructor to take one argument.
A human can rationalize circular logic, but a compiler rather likes things to be composed linearly. The compiler can't find a constructor that takes a 'const T*' because it doesn't know where to look. The compiler can't deduce the type of the template parameter because it doesn't know if any instantiation supports such a constructor. (The compiler literally doesn't know the layout of a template class until it is instantiated.)Quote:
It finds a constructor that takes const T*. Seeing as I add a wchar_t*, the compiler can easily deduce the type of T to wchar_t and call the constructor.
This, again, is composed of circular logic. If the compiler can't determine the type of 'T' it can't create a temporary of type 'CTest<wchar_t>' in the first place.Quote:
The temporary object will then be of type CTest<wchar_t> and the compiler can deduce the type of T for the operator.
For the foreseeable future, keep this thought in your head when programming with templates: only the instantiations of templates actually exist.
Soma
I think that is the problem right there. The compiler is too inflexible. The class should not be named CTest<?>, but CTest, because however you look at it, there will only be one CTest and its specializations. Therefore, the compiler can scan for any template classes whose class name is CTest, even though it may not exist.
But I guess we don't have that luxury.
O_OQuote:
I think that is the problem right there. The compiler is too inflexible. The class should not be named CTest<?>, but CTest, because however you look at it, there will only be one CTest and its specializations. Therefore, the compiler can scan for any template classes whose class name is CTest, even though it may not exist.
But I guess we don't have that luxury.
You expect a compiler to examine a class that does not yet even potentially exist? (Edit: Yes CornedBee, that was derisive.)
And anyway, that still doesn't answer the crucial question: where does the compiler start its examination? If you were to call every 'CTest<?>' by the name 'CTest', how would the compiler determine if any 'CTest' had a constructor taking a 'const wchar_t *'? (Remember, you just named the template argument relevant 'CTest<wchar_t>' constructor as taking a 'const self::arguments::_1 *' which may or may not be the same as 'const wchar_t *'.) If you look at every 'CTest<?>' by the name 'CTest', and a construct taking a 'const wchar_t *' is found, how do you determine the template arguments over any other valid arguments? (That is, how would you determine the types to pass to the parameters of the template if a partial specialization exists that has a constructor taking a 'const wchar_t *' regardless of the types the template is instantiated with?)
By the by, this is also why you need 'template<typename T> void operator += (const typename CTest<T>::this_type &, CTest<T>&);'. Even with the existence of the companion operator the compiler can't know where to being, so it doesn't try.
Soma
But there is the thing - why should templates by special? It you provide a class definition, then the class would indeed exist.
So even if we append template before the class, it should be considered as a valid definition. The class CTest would exist, but not CTest<?> (no CTest<int>, no CTest<float>). Basically, no fully qualified class (full class type) would exist, but the general declaration of the template class, named CTest does exist.
Again, why should templates be special? It can search from the line upwards to find a definition of CTest. If it does not exist in the current file, then a header with the definition could be included.Quote:
And anyway, that still doesn't answer the crucial question: where does the compiler start its examination? If you were to call every 'CTest<?>' by the name 'CTest', how would the compiler determine if any 'CTest' had a constructor taking a 'const wchar_t *'?
If one or more specializations of the class does not exist in that header, then it's a programmer error, because this is just how normal classes works, as well. A full definition, including potential specializations. Then the compiler would be able to find the definition it seeks.
I'm not sure I understand this one.Quote:
(Remember, you just named the template argument relevant 'CTest<wchar_t>' constructor as taking a 'const self::arguments::_1 *' which may or may not be the same as 'const wchar_t *'.)
Let's look at a source:
As from what I understand, the compiler cannot create a CTest without explicit type (eg CTest<char>) because it doesn't know beforehand what function you are going to call. But if it could search inside the class for a function that takes one argument...Code:template<typename T> class CTest { CTest(const T*) {} };
If it finds one, then it could try to match the argument I pass against T and see if it can generate an implicit conversion to const T*:
In this code, the compiler should search for a constructor that takes one argument.Code:template<typename T> class CTest { CTest(const T*) {} };
int main()
{
CTest test("my test");
}
Then it can translate T = char* (because it's the argument we pass), so the constructor becomes:
CTest(const char*)
Then it will see if it can implicitly convert the argument we pass to that type. If it is successful, it calls the constructor and everyone is happy.
But if it cannot match the type, then it must complain it cannot deduce T.
If it finds several functions that may match, then it must complain ambiguous.
If that's the case (if the compiler sees those specializations, that is), then the compiler would obviously be confused and return an ambiguous error.Quote:
If you look at every 'CTest<?>' by the name 'CTest', and a construct taking a 'const wchar_t *' is found, how do you determine the template arguments over any other valid arguments? (That is, how would you determine the types to pass to the parameters of the template if a partial specialization exists that has a constructor taking a 'const wchar_t *' regardless of the types the template is instantiated with?)
Templates are templates. They aren't special.Quote:
But there is the thing - why should templates by special? It you provide a class definition, then the class would indeed exist.
And thus, the other problem: you can't say that the constructor taking a 'const the_same_type_as_the_first_template_argument *' of 'CTest' actually takes a 'const wchar_t *' because you've not yet assigned those arguments values/types. Without a fully instantiated template you can't determine if the referenced types of the template arguments support any construct.Quote:
So even if we append template before the class, it should be considered as a valid definition. The class CTest would exist, but not CTest<?> (no CTest<int>, no CTest<float>). Basically, no fully qualified class (full class type) would exist, but the general declaration of the template class, named CTest does exist.
I have no idea what you mean to be saying. C++ doesn't parse anything upwards.Quote:
Again, why should templates be special? It can search from the line upwards to find a definition of CTest. If it does not exist in the current file, then a header with the definition could be included.
Requiring that a specialization appears before its first use changes nothing about how a compiler might determine which specialization to instantiate.Quote:
If one or more specializations of the class does not exist in that header, then it's a programmer error, because this is just how normal classes works, as well. A full definition, including potential specializations. Then the compiler would be able to find the definition it seeks.
It's simple. You can't examine the values associated with the parameters of a function before it is called; by the same logic, you can't determine the types associated with template parameters until it is instantiated.Quote:
I'm not sure I understand this one.
Nope. It's much simpler than that. The compiler can't create a 'CTest' without an explicit type period. It can't because it is a template by which other types are crafted.Quote:
As from what I understand, the compiler cannot create a CTest without explicit type (eg CTest<char>) because it doesn't know beforehand what function you are going to call.
The compiler can and does search inside a class for conversion functions. That isn't the problem. The compiler doesn't know which class to search.Quote:
But if it could search inside the class for a function that takes one argument...
A generic 'T' matches everything. If I have a partial specialization of 'CTest', before the point of invocation or later in the source, named 'template <typename T> CTest<type_vector<T, T>>' that "always" has a constructor taking 'const wchar_t *' as a parameter how do you determine what value 'T' should have? You can't. The compiler can't, and the compiler doesn't try.Quote:
If it finds one, then it could try to match the argument I pass against T and see if it can generate an implicit conversion to const T*:
Your example is logically wrong, but I'll give you the befit of doubt. (That is, I'll assume your going to correct the inconsistencies.) I'll even give you some "source"--like yours--of my own for you to chew over. Actually, I'll give you two different chunks.Quote:
Then it will see if it can implicitly convert the argument we pass to that type. If it is successful, it calls the constructor and everyone is happy.
Which template class did I intend to instantiate?Code:template <typename T> class CTest {CTest(const T*){} typedef T type;};
template <typename T> class CTest<type_vector<T, T>> {CTest(const char *){} typedef type_vector<T, T> type;};
int main()
{
CTest test("my test");
}
Which instantiation is more appropriate? Why?
What would 'type' be a typedef for? And in the other case?
There is no ambiguity here; what would 'type' be a typedef for?Code:template <typename T> class CTest {CTest(){} typedef T type;}; // Note: Constructor
template <typename T> class CTest<type_vector<T, T>> {CTest(const char *){} typedef type_vector<T, T> type;};
int main()
{
CTest test("my test");
}
That is what the compiler said. The only difference is that the compiler never made it to the constructor call because it could not deduce the type of the class to search for a specific constructor.Quote:
But if it cannot match the type, then it must complain it cannot deduce T.
It couldn't determine the type to instantiate the template class with so it couldn't begin looking for ambiguities; the compiler couldn't complain of ambiguities because it couldn't be sure of any.Quote:
If it finds several functions that may match, then it must complain ambiguous.
Soma