I've inherited some code that looks like this:
///
/// A specializable function for converting a user-defined object to a string value
///
template <typename value_type>
std::string to_string(const value_type &value)
{
static_assert(!std::is_same<value_type, value_type>::value, "Unspecialized usage of to_string not supported");
return "";
}
///
/// A specializable function for converting a user-defined object from a string to a value
///
template <typename return_type>
return_type from_string(const std::string &source)
{
static_assert(!std::is_same<return_type, return_type>::value, "Unspecialized usage of from_string not supported");
}
!std::is_same<value_type, value_type>::value seems overly verbose.
Should I change these statements to static_assert(false,"...")?
I'm not sure if it was expressed this way to handle some kind of edge case, or if false is indeed equivalent.
Is std::is_same<t,t>::value always true?
The code you posted is ill formed with no diagnostic required.
Replacing it with static_assert(false, ...) makes the compiler notice the fact your code is ill-formed. The code was ill-formed before, the compiler just didn't notice it.
I have two fixes to your problem. One is a hack, but legal. The other is much cleaner, but requires you to write more code.
The first section of this answer is why your code is ill-formed. The next two are solutions.
Why is the code ill-formed?
template <typename value_type>
std::string to_string(const value_type &value)
{
static_assert(!std::is_same<value_type, value_type>::value, "Unspecialized usage of to_string not supported");
return "";
}
The primary template of to_string cannot be instantiated with any type. The C++ standard demands that all templates, including primary templates, must have a valid instantiation (which in standardese is called a valid specialization). (There are other requirements, like at least one such instantiation must have an non-empty pack if there are packs involved, etc).
You may complain that "it compiled and worked", but that is no diagnostic required means. The C++ standard places zero constraints on what the compiler does when it runs into a "ill-formed no diagnostic required" case. It can fail to detect it and blithely compile that "works". It can assume it is impossible, and generate malformed code if it does happen. It can attempt to detect it, fail, and do either of the above. It can attempt to detect it, succeed, and generate an error message. It can detect it, succeed, and generate code that emails thumbnails of every image you looked at in your browser over the last year to all of your contacts.
It is ill-formed, and no diagnostic is required.
I would avoid such code myself.
Now, one might argue that someone could somewhere specialize is_same<T,T> to return false, but that would also make your program ill formed as an illegal specialization of a template from std that violates the requirements on the template as written in the standard.
Replacing !std::is_same<value_type, value_type>::value with false will simply permit your compiler to realize your code is ill formed, and generate an error message. This is, in a sense, a good thing, as ill formed code can break in arbitrary ways in the future.
Hack way to fix it
The stupid way to fix this is to create
template<class T, class U>
struct my_is_same:std::is_same<T,U> {};
which admits the possibility of the specialization loophole. This is still code smell.
Right way to fix it
The right way to write both of these requires a bit of work.
First, to_string and from_string based off tag dispatching instead of template specialization:
namespace utility {
template<class T>struct tag_t {};
template <typename value_type>
std::string to_string(tag_t<value_type>, const value_type &value) = delete;
template <typename value_type>
std::string to_string(const value_type &value) {
return to_string(tag_t<value_type>{}, value);
}
template <typename return_type>
return_type from_string(tag_t<return_type>, const std::string &source) = delete;
template <typename return_type>
return_type from_string(const std::string &source) {
return from_string(tag_t<return_type>{}, source);
}
}
The goal is that the end user simply does a utility::from_string<Bob>(b) or utility::to_string(bob) and it works.
The base ones bounce to tag-dispatches. To customize, you overload the tag-dispatch versions.
To implement the to/from string, in the namespace of Bob write these two functions:
Bob from_string( utility::tag_t<Bob>, const std::string& source );
std::string to_string( utility::tag_t<Bob>, const Bob& source );
notice they are not templates or specializations of templates.
To handle types in std or built-in types, simply define similar overloads in namespace utility.
Now, ADL and tag dispatching take you to the correct to/from string function. No need to change namespaces to define to/from string.
If you call to_string or from_string without a valid tag_t overload, you end up calling the =delete and getting an "overload not found" error.
Test code:
struct Bob {
friend std::string to_string( utility::tag_t<Bob>, Bob const& ) { return "bob"; }
friend Bob from_string( utility::tag_t<Bob>, std::string const&s ) { if (s=="bob") return {}; exit(-1); }
};
int main() {
Bob b = utility::from_string<Bob>("bob");
std::cout << "Bob is " << utility::to_string(b) << "\n";
b = utility::from_string<Bob>( utility::to_string(b) );
std::cout << "Bob is " << utility::to_string(b) << std::endl;
Bob b2 = utility::from_string<Bob>("not bob");
std::cout << "This line never runs\n";
(void)b2;
}
Live example.
(Use of friend is not required, the function just has to be in the same namespace as Bob or within namespace utility).
Related
The following code depends on whether the concept is defined before or after the class. The intent is to check, whether a Quantity can be constructed from a T. I have stripped this down, only showing the test for copy construction.
template <typename T>
concept is_compatible = requires( T & t ) { Quantity( t ); }; // note: not Quantity<T> !
template <typename T>
class Quantity
{};
class X{};
int main()
{
std::cout << is_compatible<Quantity<X>>;
std::cout << is_compatible<X>;
}
With the given code, the output is 00, not the intended 10. To get the intended result, the concept has to be defined after the class.
I would have expected one of two things for the code above:
It just works as intended
A warning or error is given by the compiler (to hint at our mistake, if it is one) Note that when using e.g. Quantity<T> instead of Quantity in the concept, a compiler error is issued, because Quantity<T> does not make sense at that point.
I would not have expected the third, silent option! There might be a "good reason" for this, "because compilers work a certain way", but I find this pretty flawed.
What would be a better way to write this code, if the behaviour is correct?
There might be a "good reason" for this, "because compilers work a certain way", but I find this pretty flawed.
Concepts wouldn't be as useful without it. You wouldn't be able to check that a function call expression relying on ADL is valid. Your concept is satisfied by this pair of declarations
namespace ns {
struct X {};
void Quantity(X) {}
}
And it will be satisfied even if is_compatible is defined before ns.
Now, if your concept has nothing to do with ADL, and your expression is indeed intended to be a function-styled cast, then simply qualify the type (or template name)
template <typename T>
concept is_compatible = requires( T & t ) { ::Quantity( t ); };
// ^ -- in the absence of a preceding declaration, error here
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.
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…
You've already got the answer to your question, but the context of the question is likewise interesting for readers of this post, so I thought it'd be valuable to mention that you may use tag dispatching for a use case such as your own, to:
Set up default configuration strings, at compile time, for specific types (e.g. int) or groups of types (e.g. std::map<K, V> for generic K and V)
Without tag dispatching, this can be tricky as you may not partially specialize a function template.
E.g.:
#include <map>
#include <string>
#include <iostream>
template <typename T>
class Config {
public:
static const std::string& get() { return Config::getString(); }
static void set(const std::string& value) { Config::getString() = value; }
Config(Config const&) = delete;
void operator=(Config const&) = delete;
private:
static std::string& getString() {
static std::string s(defaultString(dispatch_tag<T>{}));
return s;
}
template <typename U>
struct dispatch_tag {};
// Default string unless specified for specific types below.
template <typename U = T>
static constexpr std::string_view defaultString(dispatch_tag<U>) {
return "default";
}
// Default config strings for a select number of types.
static constexpr std::string_view defaultString(dispatch_tag<int>) {
return "default int";
}
template <typename K, typename V>
static constexpr std::string_view defaultString(
dispatch_tag<std::map<K, V>>) {
return "default map";
}
};
int main() {
std::cout << Config<int>::get() << "\n"; // default int
std::cout << Config<std::string>::get() << "\n"; // default
std::cout << Config<std::map<int, int>>::get() << "\n"; // default map
Config<int>::set("custom int");
Config<std::map<int, int>>::set("custom int-int map");
std::cout << Config<int>::get() << "\n"; // custom int
std::cout << Config<std::map<int, int>>::get() << "\n"; // custom int-int map
std::cout << Config<std::map<int, char>>::get() << "\n"; // default map
}
This does not solve, however, that you would like to (based on your comments to your own post) specify, at run-time, the value of the fall-back default configuration string for generic types (say, std::map<K, V>).
I have trouble understanding the order of template instantiation. It seems that the compiler does not consider a function if it is defined "too late." The following steps illustrates the main ideas of the code below:
The framework should provide a free function convert<From, To> if it can find a working overload for the function generate.
The function to<T> is a shortcut for convert<From,To> and should only work if convert<From,To> is valid.
Users should be able to provide an overload of generate and be able to use to and convert.
The corresponding code:
#include <string>
#include <utility>
#include <iostream>
// If I move the code down below at [*] to this location, everything works as
// expected.
// ------------- Framework Code -------------
// Anything that can be generated can also be converted to a string.
template <typename From>
auto convert(From const& from, std::string& to)
-> decltype(
generate(std::declval<std::back_insert_iterator<std::string>&>(), from)
)
{
to.clear();
auto i = std::back_inserter(to);
return generate(i, from);
}
// Similar to convert, except that it directly returns the requested type.
template <typename To, typename From>
auto to(From const& f) -> decltype(convert(f, std::declval<To&>()), To())
{
To t;
if (! convert(f, t))
throw std::invalid_argument("invalid conversion");
return t;
}
// ------------- User Code -------------
// [*] Support arithmetic types.
template <typename Iterator, typename T>
auto generate(Iterator& out, T i)
-> typename std::enable_if<std::is_arithmetic<T>::value, bool>::type
{
// Note: I merely use std::to_string for illustration purposes here.
auto str = std::to_string(i);
out = std::copy(str.begin(), str.end(), out);
return true;
}
int main()
{
uint16_t s = 16;
std::cout << to<std::string>(s) << std::endl;
return 0;
}
The problem in the following code is that it only works if the function generate appears before the definition of convert and to. How can I work around this problem?
Maybe my mental model is wrong here, but I thought the template when the compiler sees to<std::string>(uint16_t), it starts going backwards to and instantiate as needed. Any guidance would be appreciated.
The compiler does not know of the existence of generate by the time it sees the definition of convert and to, as you have already guessed yourself. Contrary to what you thought, it doest not though sort of put the definitions of convert and to "on hold" until it sees what generate is. To workaround this problem you need to forward declare generate, what can be done using the following construction:
template <typename Iterator, typename T>
auto generate(Iterator& out, T i)
-> typename std::enable_if<std::is_arithmetic<T>::value, bool>::type;
This should appear right before the definition of convert, so that the compiler knows generate actually exists and also is a function by the time it compiles convert and to. This way the compiler can check the syntax and guarantee it is a valid call to generate, even before it knows what generate actually does, since all it needs to do at this point is check if the types of the arguments as well as of the return value match, according to the rules defined by the language standard.
By doing this, you naturally enforce a specific type signature for generate (remember the compiler is required to check the types when it compiles convert and to!). If you don't want to do that, and you probably don't, then the best approach is to expect a further template argument to convert and to likewise, which you expect to be callable, that is, which you can use as in a function call:
template <typename From, typename Generator>
auto convert(From const& from, std::string& to, Generator generate)
-> decltype(
generate(std::declval<std::back_insert_iterator<std::string>&>(), from)
)
{
to.clear();
auto i = std::back_inserter(to);
return generate(i, from);
}
These kind of objects are commonly known as callable objects.
The drawback to this approach is that because c++ unfortunately does not support concepts yet, you can't do much to enforce the requirements the callable object generate should attend to. Nonetheless, this approach is what the std library successfully uses for its algorithms.
The advantage of this approach is that it can be very flexible, for any possible callable object, which minimally attends to the type requirements, can be used. That includes free functions, function objects, member functions through binding, among others. Not to mention the user is absolutely free to choose the name she wants for her callable object instead of being forced to use generate, as your initial idea would require if it were valid c++.
Now to call this modified version of convert using the free function generateyou defined, you would do that:
to<std::string>(s, generate<std::back_insert_iterator<std::string>, uint16_t>);
That isn't very nice, because since you must explicitly state the template arguments, this approach fails to take full advantage of the fact generate is a template function. Fortunately this inconvenience can be overcome by the use of function objects, like the following for instance:
struct Generator
{
template <typename Iterator, typename T>
auto operator()(Iterator& out, T i)
-> typename std::enable_if<std::is_arithmetic<T>::value, bool>::type
{
// Note: I merely use std::to_string for illustration purposes here.
auto str = std::to_string(i);
out = std::copy(str.begin(), str.end(), out);
return true;
}
};
The previous call would become simply
to<std::string>(s, Generator());
taking full advantage of its tamplate nature.
At any rate, if I got the idea correctly, this part of the code is of responsability of the user, so she, as she deserves, have full autonomy to decide which way she prefers.
Currently, I have a function template like this that converts a vector into a string (just a natural string, separating the elements with a comma):
//the type T must be passable into std::to_string
template<typename T>
std::string vec_to_str(const std::vector<T> &vec);
As you can see, this is only meant for vectors whose elements can be passed into the built-in std::to_string function (such as int, double, etc.)
Is it considered a good practice to document with comments the allowed T? If not, what should I do? Is it possible to enforce this in a better way?
With static_assert and some expression SFINAE, you can have a nice compile time error message:
template<typename T>
constexpr auto allowed(int) -> decltype(std::to_string(std::declval<T>()), bool())
{
return true;
}
template<typename>
constexpr bool allowed(...)
{
return false;
}
template<typename T>
std::string vec_to_str(const std::vector<T>& vec)
{
static_assert(allowed<T>(0), "Invalid value type.");
return "";
}
struct foo {};
int main()
{
std::vector<int> v_int;
vec_to_str(v_int);
std::vector<foo> v_double;
vec_to_str(v_double); // static_assert fires here
}
Since std::to_string is a C++11 feature, I guess you might be open to a C++11 solution. In this particular case, you can use the trailing return type in a sfinae manner:
template <typename T>
auto vec_to_str(const std::vector<T>& vec) -> decltype(std::to_string(std::declval<T>()));
which would fail to substitute (and eliminate that overload) if the value-type does not work for to_string. But still, that's not necessarily the most eye-pleasing way to document and enforce the rule. There is probably a pre-C++11 version of the above Sfinae trick too, but it won't be any prettier.
In general, I would say that it's fine to simply document it in the comments (possibly with a doxygen tag, like \tparam). You could use a concept-check mechanism, in the style of Boost.Concept-Check if you want.
As a side note, for this specific case, I might recommend that you rely on the std::ostream operator << instead of the to_string function, since it is more likely that custom types (e.g., a 2D vector or something) will be equipped with an overload for outputting to a stream.
Until concepts arrive, you can use decltype with an anonymous type parameter:
template<typename T,
typename = decltype(std::to_string(std::declval<T>()))>
std::string vec_to_str(const std::vector<T> &vec);
int foo(int){
...
}
Any ideas?
When you are not actually using the parameter in the function but do not want to break the public method signature.
One case where you define a function but do not name the parameter is given in Scott Meyers's "Effective C++, 3rd edition", Item 47:
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d,
std::random_access_iterator_tag)
{
iter += d;
}
is used in:
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
doAdvance( iter, d,
typename std::iterator_traits<IterT>::iterator_category() );
}
Essentially, the third parameter in doAdvance is a type unaccompanied by a variable name, i.e. it is an unnamed parameter. Having an unnamed parameter isn't a problem, since that argument is only used during the resolution of which overloaded function to use. I discuss these topics in this related SO question.
This is how the compiler differentiates postfix and prefix increment operators -- X operator++() is prefix, X operator++(int) is postfix.
Because the int is never actually used in the postfix operator, there's no reason to give it a name. This is generally why you get unnamed parameters, to overload a function or operator to mean two slightly different things. This is a common trick in template metaprogramming as used in Loki and Boost.
In some cases, compilers will warn of unused parameters, unless you leave off the identifier name as you show. It's also potentially useful to show that a parameter is unused. Other than that, I have no idea. You certainly can't leave the identifier off if you use the parameter.
Extra parameters can be used by SFINAE, for overload resolution or dispatch depending on type properties.
#include <boost/utility/enable_if.hpp>
#include <boost/type_traits.hpp>
#include <boost/call_traits.hpp>
#include <iostream>
#include <string>
// use SFINAE enable this function for numerical types only
template <class T>
double foo(T t, typename
boost::enable_if<boost::is_floating_point<T> >::
type* dummy = 0) {
double x = static_cast<double>(t);
return x;
}
// use extra parameter for overload resolution
struct PlanA {};
struct PlanB {};
void bar(const PlanA&) {
std::cout << "using plan A...\n";
}
void bar(const PlanB&) {
std::cout << "using plan B...\n";
}
// use type traits to choose which function to call
template <typename T>
double fooBar(T t) {
return dispatch(t, boost::is_convertible<T, double>());
}
double dispatch(double t, const boost::true_type&) {
std::cout << "convertible to double\n";
return t+0.1;
}
template <typename T>
double dispatch(const T&, const boost::false_type&) {
std::cout << "not convertible to double\n";
return 0.0;
}
int main () {
foo(1.0);
// foo(1); won't compile
bar(PlanA());
bar(PlanB());
std::string name = "John Dow";
fooBar(1.0);
fooBar(name);
}
You do it when you're implementing a function of some signature, don't actually need the parameter (quite common in hierarchies behind an abstract), and need to compile without warnings. Since all projects should be set to break when there's a warning, and this comes up a LOT in OOP/GP, it's a very, very common thing to do. It's so common in fact that many libraries have a mylibUNUSED() macro for better legibility.
Note that from the fact that the compiler doesn't care about the actual parameter name comes the fact that you can use different parameter names for a function( template)'s declaration than for it's definition.
This is useful when you have a library that you ship as headers and as lib file and everyone using your API will not want to have to re-compile everything just because, inside of the library, you change the names of some parameter(s). (You probably have to have been in a multi-MLoC C++ project to appreciate that.)
I think you mean in the declaration of functions. Actually, you can also omit the indentifier name in the definition of functions, but this will cause a compilation error if the parameter is used, which is likely to happen.