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);
Related
I'm piecing together a C++20 puzzle. Here's what I want to do: Function append_params will concatenate the url together with additional query parameters. To make design this in a dynamic and extensible way, I wanted to write a concept such that
it allows types that an std::string can be constructed from
it allows types convertible to string using std::to_string()
template<typename... Ts> requires requires(T a) { std::to_string(a); }
auto append_params(std::pmr::string url, Ts... args) {
}
it works for a pack of parameters
I've found useful information on point 2) here. However, for point 1) and 3) I'm rather clueless (I'm also new to concepts). How can I constrain the whole parameter pack (what's the syntax here?) and how can I make sure that from every parameter I can construct an std::string object?
Also, I would have to know at compile time if I want to use std::strings constructor or std::to_string to handle the case.
When developing a concept, always start with the template code you want to constrain. You may adjust that code at some point, but you always want to start with that code (unconstrained).
So what you want is something like this:
template<typename... Ts>
auto append_params(std::string &url, Ts... &&args)
{
return (url + ... + std::forward<Ts>(args) );
}
This doesn't work if any of the types in args are not std::strings or something that can be concatenated with std::string. But you want more. You want to take types which std::to_string can be used on.
Note that this is a pretty bad interface, since std::to_string cannot be extended by the user. They can make their types convertible to std::string or some other concatenate-able string type like string_view. But users cannot add overloads to std library stuff, so they can't make a type to_stringable.
Regardless, to allow to_string to work, we would need to change the code to concatenate with this expression: std::to_string(std::forward<Ts>(args)). But that would only take types that to_string works on, not types convertible to a string.
So what we need is a new function which will use to_string if that is appropriate or just return the original expression if it can be concatenated directly.
So we have two kinds of types: those that are inherently string-concatenatable, and those which are to_string-able:
template<typename T>
concept is_direct_string_concatenatable = requires(std::string str, T t)
{
{str + t} -> std::same_as<std::string>;
};
template<typename T>
concept is_to_stringable = requires(T t)
{
{std::to_string(t)} -> std::same_as<std::string>;
};
//Combines the two concepts.
template<typename T>
concept is_string_concatenable =
is_direct_string_concatenatable<T> ||
is_to_stringable <T>;
template<is_string_concatenable T>
decltype(auto) make_concatenable(T &&t)
{
//To_string gets priority.
if constexpr(is_to_stringable<T>)
return std::to_string(std::forward<T>(t));
else
return std::forward<T>(t);
}
So now your template function needs to use make_concatenable and the concept:
template<is_string_concatenable ...Ts>
auto append_params(std::string url, Ts&& ...args)
{
return (url + ... + make_concatenable(std::forward<Ts>(args)));
}
Consider following code:
int64_t signed_vector_size(const std::vector v){
return (int64_t)v.size();
}
This does not work since std::vector is a template. But my function works for every T!
Easy fix is to just do
1)
template<typename T>
int64_t signed_vector_size(const std::vector<T>& v){
return (int64_t)v.size();
}
or make the template implicit
2)
int64_t signed_vector_size(const auto& v){
return (int64_t)v.size();
}
Or concept based solution, option 3.
template<class, template<class...> class>
inline constexpr bool is_specialization = false;
template<template<class...> class T, class... Args>
inline constexpr bool is_specialization<T<Args...>, T> = true;
template<class T>
concept Vec = is_specialization<T, std::vector>;
int64_t signed_vector_size(const Vec auto& v){
return (int64_t)v.size();
}
I like the second solution, but it accepts any v, while I would like to limit it to the vector type only. Third is the best when just looking at the function, but specifying concepts is a relatively a lot of work.
Does C++20 syntax has any shorter way for me to specify that I want any std::vector as an argument or is the 1. solution the shortest we can do?
note: this is silly simplified example, please do not comment about how I am spending too much time to save typing 10 characters, or how I am sacrificing readability(that is my personal preference, I understand why some people like explicit template syntax).
A template is just a pattern for something. vector is the pattern; vector<int, std::allocator<int>> is a type. A function cannot take a pattern; it can only take a type. So a function has to provide an actual type.
So if you want a function which takes any instantiation of a template, then that function must itself be a template, and it must itself require everything that the template it takes as an argument requires. And this must be spelled out explicitly in the declaration of the function.
Even your is_specialization falls short, as it assumes that all template arguments are type arguments. It wouldn't work for std::array, since one of its arguments is a value, not a type.
C++ has no convenient mechanism to say what you're trying to say. You have to spell it out, or accept some less-than-ideal compromise.
Also, broadly speaking, it's probably not a good idea. If your function already must be a template, what would be the harm in taking any sized_range? Once you start expanding templates like this, you're going to find yourself less likely to be bound to specific types and more willing to accept any type that fulfills a particular concept.
That is, it's rare to have a function that is specific enough that it needs vector, but general enough that it doesn't have requirements on the value_type of that vector too.
Note that is not valid in standard C++20, but you can achieve exactly what you want with the following syntax that is supported by GCC as an extension.
std::vector<auto>
Which is shorthand for std::vector<T> where T is unconstrained.
http://coliru.stacked-crooked.com/a/6422d0284d299b85
How about this syntax?
int64_t signed_vector_size(const instance_of<std::vector> auto& v){
return (int64_t)v.size();
}
Basically we want to be able to say "this argument should be an instance of some template". So, say that?
template<template<class...>class Z, class T>
struct is_instance_of : std::false_type {};
template<template<class...>class Z, class...Ts>
struct is_instance_of<Z, Z<Ts...>> : std::true_type {};
template<class T, template<class...>class Z>
concept instance_of = is_instance_of<Z, T>::value;
int64_t signed_vector_size(const instance_of<std::vector> auto& v){
return (int64_t)v.size();
}
that should do it. Note that I don't make a Vec alias; you can pass in partial arguments to a concept. The type you are testing is prepended.
Live example.
Now, I'd actually say this is a bit of an anti-pattern. I mean, that size? Why shouldn't it work on non-vectors? Like, std::spans or std::deques.
Also, instance_of doesn't support std::array, as one of the arguments isn't a type. There is no way to treat type, template and value arguments uniformly in C++ at this point.
For each pattern of type, template and value arguments you'd need a different concept. Which is awkward.
I want to write a functor, that compares a string-like type to another. One side of the comparison is set once at initialization and reused.
The types I want to support in any case are std::basic_string, std::basic_string_view and char*, but other like std::byte* as well as std::array and std::vector are interesting, too.
My first implementation looks like this:
template<typename StringType>
class StrcmpAlgorithm {
StringType pattern;
public:
StrcmpAlgorithm(const StringType& p) : pattern(p) {}
template<typename InputString>
bool operator()(const InputString& input)
{
return input == pattern;
}
};
However, this solution is quite limited, as the usage of the equals operator limits the types I can use and might even do the wrong thing (for example when comparing with a C string).
I'm not really sure how I should approach this. Provide multiple overloads for the call operator? Use constexpr-if and check for the type?
Essentially, having the lhs of the comparison a template parameter (StringType) and the rhs a different template parameter (InputString) leads to a combinatorical problem, even if the STL already provides some of all possible comparions. Eliminating one of those would make the whole thing much easier. But for the pattern member, I need at least the possibility to store strings with different character widths, as well as having the choice between a value and a reference type.
I believe the generally best approach is template specialization, meaning that the template class will behave differently on char*, std::array, etc.
Note, that if you have std::basic_string_view, then you can convert all of the types you mentioned to a string view, and thus use the built-in comparison function. So in your case I would implement it a bit different:
template<typename T>
std::string_view toStringView(const T& object)
{
// covers containers (std::string_view, std::array, std::vector, std::string)
return std::string_view(object.data(), object.size());
}
template<typename T>
std::string_view toStringView(const T* ptr)
{
// Pointer, in this case you would need a null-terminator string.
return std::string_view(reinterpret_cast<const char*>(ptr));
}
template<typename StringType>
class StrcmpAlgorithm {
StringType pattern;
public:
StrcmpAlgorithm(StringType p) : pattern(std::move(p)) {}
template<typename InputString>
bool operator()(const InputString& input)
{
return toStringView(input) == toStringView(pattern);
}
};
Of course, this example can and should be improved to support std::wstring_view and to fail on incompatible types (such as char**).
No C++11 or Boost :(
I have a function with the following signature.
template<class INPUT_ITR, class OUTPUT_ITR>
void DoWork(const INPUT_ITR in_it, const INPUT_ITR in_it_end, OUTPUT_ITR out_it, OUTPUT_ITR out_it_end, CONTEXT_DATA)
Normally some complex processing takes place between the input and output.. but sometimes a no-op is required, and the data is simply copied. The function supports in-place operations if the input and output data types are the same. So I have this code.
if (NoOp)
{
if (in_it != out_it)
{
copy(in_it, in_it_end, out_it);
}
}
If an in-place operation has been requested (the iterator check), there is no need to copy any data.
This has worked fine until I call the function with iterators to different data types (int32 to int64 for example). Then it complains about the iterator check because they are incompatible types.
error C2679: binary '!=' : no operator found which takes a right-hand operand of type 'std::_Vector_iterator<std::_Vector_val<std::_Simple_types<unsigned __int64>>>
This has left me a bit stumped. Is there an easy way to perform this check if the data types are the same, but simply perform the copy if they are different types?
Thanks
You can extract the test into a pair of templates; one for matching types, one for non-matching types.
template <class T1, class T2>
bool same(T1 const &, T2 const &) {return false;}
template <class T>
bool same(T const & a, T const & b) {return a == b;}
Beware that this can give confusing results when used with types that you'd expect to be comparable. In C++11 (or with Boost, or a lot of tedious mucking around with templates) you could extend this to compare different types when possible; but that's beyond what you need here.
Also, note that you're relying on formally undefined behaviour, since iterators over different underlying sequences aren't required to be comparable. There is no way to tell from the iterators themselves whether this is the case.
I came up with a workaround. Any better suggestions welcome.
Overload the function to provide an in-place version. It's up to the user to request in-place now (in-place using the old function will perform the redundant copy in case of no op).
template<class ITR>
void DoWork(const ITR it, const ITR it_end, CONTEXT_DATA)
{
if (! NoOp)
{
DoWork(it, it_end, it, it_end, sSourceSpec, sDestSpec);
}
}
You could use std::iterator_traits and a custom is_same type trait since you don't have c++11:
template<class T, class U>
struct is_same
{
static const bool value = false;
};
template<class T>
struct is_same<T, T>
{
static const bool value = true;
};
if(!is_same<typename std::iterator_traits<INPUT_ITR>::value_type,
typename std::iterator_traits<OUTPUT_ITR>::value_type>::value)
{
copy(...);
}
Given int a;, I know that the following returns the largest value that a can hold.
numeric_limits<int>::max()
However, I'd like to get this same information without knowing that a is an int. I'd like to do something like this:
numeric_limits<typeof<a>>::max()
Not with this exact syntax, but is this even possible using ISO C++?
Thanks, all. Aurélien Vallée's type_of() comes closest, but I'd rather not add anything extra to our codebase. Since we already use Boost, Éric Malenfant's reference to Boost.Typeof led me to use
numeric_limits<BOOST_TYPEOF(m_focusspeed)>::max()
I'd never used it before. Again, thanks for so many well-informed responses.
template<typename T>
T get_lim( const T & x)
{
return numeric_limits<T>::max();
}
the good thing is that you can use it without explicitly specifying T:
size_t l = get_lim(34);
Just FWIW, C++ 0x will also have decltype, which is nearly the same as typeof. They picked a new name primarily because the semantics are different in one case. The existing implementation of typeof (gcc) drops references from types, so typeof(int &) == int. The standard requires that decltype(int &) == int&. This doesn't matter very often, but they decided to use a different name to prevent any silent changes to existing code.
numeric_limits is what is known as a type trait. It stores information relative to a type, in an unobtrusive way.
Concerning your question, you can just define a template function that will determine the type of the variable for you.
template <typename T>
T valued_max( const T& v )
{
return numeric_limits<T>::max();
};
template <typename T>
T valued_min( const T& v )
{
return numeric_limits<T>::min();
};
or just create a small type returning structure:
template <typename T>
struct TypeOf
{
typedef T type;
};
template <typename T>
TypeOf<T> type_of( const T& v )
{
return TypeOf<T>();
}
int a;
numeric_limits<type_of(a)::type>::max();
numeric_limits<typeof(a)> works with GCC. (If you have it in standards-compliant mode, you may need to use __typeof__ instead.)
Starting with C++11, you can use decltype():
numeric_limits<decltype(a)>::max()
See also Difference between decltype and typeof?.