Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe to reference a C++ template type having a template parameter that's not compatible with the template?

In the following code sample, I define a class DT (my default type) which I want to be able to pass as the parameter(s) for an arbitrary template. In this example, I pass DT as the key and value parameters for std::map. I don't actually ever try to instantiate map<DT,DT>, I just want to use map<DT,DT> as the template parameter for a templatized function (in this sample, the function f()) that never actually references the type -- it's only used to produce a type-specific instance of the function. (Note that you can't actually instantiate a std::map<DT,DT> since the key of a map must be comparable, but DT is not.)

#include <iostream>
#include <map>

using namespace std;

class DT {};

template <typename T>
string f() {
    return "foo";
}

int main() {
    cout << f<map<DT,DT>>() << endl;
    return 0;
}

This seems to work fine using g++. I even tried passing DT for all four map parameters (overriding the default comparator, and allocator types). Still works. But I'm worried that this technique might fail with some other template, or with some other compiler. So my question is: is this always safe for any template on any c++ compiler compliant with, say, the c++11 standard (and later standards). In other words, is it always safe to pass a completely incompatible type as the parameter for a template, so long as you never try to instantiate that template?

If you're wondering why on earth I'd want to do such a thing, I'm trying to setup a class where I can store type-dependent configuration strings. It will have these two methods:

template<typename T>
const string& get<T>() const;

template<typename T>
void set<T>(const string& value);

I have it largely working to my satisfaction. It has several nice features. For example, types int, const int, int&, const int&, etc are all treated as the same type (which is what I want). And you can store a configuration string for a base class that is later retrievable by a derived type if no entry is found for the more specific derived type. But for the case of say, a std::map, I'd like to be able to store a default configuration string using type map<DT,DT> that will later be returned as a match for any map<Key,Value> when no entry is found for the specific map type at hand. If the above code is valid, then I think I can produce the desired behavior.

like image 505
Matthew Busche Avatar asked Oct 28 '25 17:10

Matthew Busche


1 Answers

Unfortunately, I believe, the standard does not guarantee that std::map<DT, DT> will not be instantiated. [temp.inst]/1 only specifies that

Unless a class template specialization has been explicitly instantiated or explicitly specialized, the class template specialization is implicitly instantiated when the specialization is referenced in a context that requires a completely-defined object type or when the completeness of the class type affects the semantics of the program. […]

Note that this only tells us when a template is guaranteed to be instantiated, it does not give any guarantees that the template will not be instantiated if such an instantiation is not required. [temp.inst]/10 only gives such a guarantee for

[…] a function template, a variable template, a member template, a non-virtual member function, a member class, a static data member of a class template, or a substatement of a constexpr if statement ([stmt.if]), unless such instantiation is required. […]

Note the absence of class templates from this list. Thus, I believe, a compiler would theoretically be allowed to instantiate std::map<DT, DT> even thought it would not be necessary to do so. If instantiating the template std::map with DT as key and value type would be invalid, you'd have a problem. I am unable to find any guarantees concerning instantiating std::map with a key type that does not support a comparison operator. While I would expect this to just work with basically any implementation, I do think that an implementation would theoretically be allowed to, e.g., have a static_assert that checks whether the key type does fulfill the necessary requirements. [res.on.functions]/1 would seem to apply (emphasis mine):

In certain cases (replacement functions, handler functions, operations on types used to instantiate standard library template components), the C++ standard library depends on components supplied by a C++ program. If these components do not meet their requirements, the Standard places no requirements on the implementation.

Thus, I think that, strictly speaking, the standard does not guarantee that using std::map<DT, DT> will work…

If you simply want to use std::map<DT, DT> as a sort of tag type to indicate a special case, I would suggest to just not use std::map but something else, for example:

template <typename, typename>
struct default_config_tag;

and then default_config_tag<DT, DT> or just DT as your tag (not sure if you need the argument to be an instance of a template with two type parameters) should be sufficient…

like image 187
Michael Kenzel Avatar answered Oct 31 '25 06:10

Michael Kenzel