lmi
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [lmi] How does T differ from std::set<T>::key_type?


From: Vadim Zeitlin
Subject: Re: [lmi] How does T differ from std::set<T>::key_type?
Date: Fri, 14 May 2010 18:10:13 +0200

On Fri, 14 May 2010 01:05:58 +0000 Greg Chicares <address@hidden> wrote:

GC> Consider the patch below [0],

 I'll use the equivalent but self-contained test instead for my explanation
below:

        % cat -n template_str.cpp
             1  #include <string>
             2  #include <set>
             3
             4  template <typename T>
             5  #ifndef BREAK_ME
             6  int test(std::set<T> const&, typename std::set<T>::key_type 
const&)
             7  #else
             8  int test(std::set<T> const&, T const&)
             9  #endif
            10  {
            11      return 7;
            12  }
            13
            14  int main()
            15  {
            16      std::set<std::string> s;
            17      return test(s, "foo");
            18  }

GC> which causes the 'contains_test' unit test to fail with gcc-3.4.5,
GC> como-4.3.3, and the antique borland-5.5.1, whereas without this patch
GC> all these tests succeed with all those compilers.

 So that the patch becomes equivalent to adding "-DBREAK_ME" to the
compiler flags. With this being said, the expected behaviour would be that
the code compiles by default and fails to compile with -DBREAK_ME. And, lo
and behold, this is indeed what happens with both g++-{3.3,4.3,4.4,4.5} and
msvc{9,10}.

GC> Why? Isn't 'typename std::set<T>::key_type' the same as just plain 'T'?
GC> The argument type is, say, char const[8], whereas T is deduced to be
GC> std::string, but why does the conversion to string work with HEAD but
GC> fail with the patch?

 The key_type is indeed just a typedef for T but this is not what matters
here as we're speaking about the template deduction done before any
user-defined conversions (such as "char const[]" to std::string) are
considered. This is probably a too brief explanation so I'll continue with
a more detailed one, sorry if it's too detailed but I wanted to make it as
clear as possible because it took me some time to understand what was going
on myself.


 So, first of all, it is pretty clear why the code doesn't compile with
-DBREAK_ME: the compiler needs to instantiate the template function for the
(std::set<std::string>, char const[]) parameter list. But this matches
equally well the function for T=std::string (exact match for first argument
but not the second one) and for T=char[] (exact match for the second
argument but not the first one). The compiler can't choose between them
because the fact that char[] is convertible to std::string but
std::set<std::string> is not convertible to std::set<char[]> simply doesn't
enter into play here (this is only checked later, once the template
specialization is chosen). Granted, g++ error message is poor here as it
doesn't tell you what the problem actually is. MSVC is nicer though:

% cl /DBREAK_ME /EHsc /W4 template_str.cpp
template_str.cpp
template_str.cpp(17) : error C2782: 'int test(const std::set<T> &,const T &)' : 
template parameter 'T' is ambiguous
        template_str.cpp(8) : see declaration of 'test'
        could be 'const char [4]'
        or       'std::string'

This is quite clear, isn't it? I didn't bother to look up the paragraph of
the Standard which is relevant here because we all know that this is what
is supposed to happen in such case anyhow. But e.g. 14.8.2.4/5 happens to
provide a similar, although simpler, example.


 The more interesting question is why does it work without -DBREAK_ME.
Informally, you can say that here the compiler must deduce T from the first
argument type because it can't deduce it from the second one: you can't
expect the poor thing to find T from the fact that std::set<T>::key_type is
char[]. And once it deduced T from the first argument, it has its template
instantiation and now the usual rules for calling functions in C++ apply,
including applying the user-defined conversions.

 And in fact this is exactly what the compiler must do to conform to the
Standard: according to 14.8.2.4/4 "T" here appears in one of "nondeduced
contexts" and so can't influence the result of the final deduction. You can
also see this by observing that "P<T>::U" is not one of the items of the
list of 14.8.2.4/9 and from the remark in /11.


 To summarize, it's not the final type value that matters here but how it's
specified: by allowing the compiler to deduce the template parameter from
this type, you force it to reject the code because of an ambiguity. While
keeping the more complex version which only uses T in nondeduced context
you prevent this from happening. So the current code is fine and, for once,
also works in practice. But if you wanted you probably could also use
something like

        template <typename T, typename U>
        int test(std::set<T> const&, U const&);

and rely on compilation errors inside the function implementation if a
wrong type is used for the second parameter.


GC> BTW, I would be glad to learn whether this unit test (the version in HEAD)
GC> fails with any other compiler you may have available.

 It passes with msvc{9,10} too.

 Regards,
VZ

reply via email to

[Prev in Thread] Current Thread [Next in Thread]