Strongly typed c++ varargs [duplicate] - c++

This question already has answers here:
C++ parameter pack, constrained to have instances of a single type?
(7 answers)
C++ Multiple function parameters with varargs of a specific type [duplicate]
(2 answers)
Why must C++ function parameter packs be placeholders or pack expansions?
(3 answers)
Can I create a function which takes any number of arguments of the same type?
(3 answers)
Closed 6 months ago.
I'm writing a utility function in c++ 11 that adds an element of a single type to a vector. Most variable argument docs/guides I've found show a template with the typedef type, but I'm looking to only allow a single type for all the variable arguments (const char*). The following is the relevant snippet of code:
Item.hpp:
// Guard removed
#include <vector>
class Item {
public:
Item(const char* n, bool (*optionChange)(uint8_t), const char*...);
private:
std::vector<const char*> options;
void addOption(const char*);
}
Item.cpp:
#include "Item.hpp"
void Item::addOption(const char* option) {
options.push_back(option);
}
Item::Item(
const char* n,
bool (*optionChange)(uint8_t),
const char* opts...
): name(n), type(MENU_TYPE_OPTS), selectedOpt(0) {
addOption(opts...); // Doesn't compile
}
Compilation of the above code fails with the message error: expansion pattern 'opts' contains no argument packs.

Use a variadic template. With variadic template, also all types can be different, but you can request that they are all the same via SFINAE
#include <type_traits>
#include <tuple>
template <typename ...T>
std::enable_if_t<
std::is_same_v< std::tuple<const char*, T...>,
std::tuple<T...,const char*>>
,void>
foo(T...t) {}
int main() {
const char* x;
int y;
foo(x,x,x,x); // OK
foo(x,y,x); // error
}
This is based on a neat trick to check if all types of a variadic pack are the same type (i'll add the referene to the original when I find it). std::tuple<const char*, T...> and std::tuple<T...,const char*> are only the same type when all Ts are const char*. std::enable_if will discard the specialization when the condition (all Ts are const char*) is not met and attempting to call it results in a compiler error.
This is rather old fashioned and works already in C++11 (apart from the _v/_t helpers). I suppose in C++20 there are less arcane ways to require all Ts to be const char*.
I missed that it is a constructor and you cannot do return-type-SFINAE on a constructor. It just needs to be a little more convoluted:
#include <type_traits>
#include <tuple>
struct foo {
template <typename ...T,
std::enable_if_t<
std::is_same_v< std::tuple<const char*, T...>,
std::tuple<T...,const char*>
>,
bool
> = true>
foo(T...t) {}
};
int main() {
const char* x;
int y;
foo f1(x,x,x,x); // OK
foo f2(x,y,x); // error
}
When the condition is met the last template parameter is non-type bool and has a default value of true. It's only purpose is to fail when the condition is not met (hence it does not need to be named).

You can't expand variadic arguments like parameter packs. You either have to switch to another approach (like the parameter packs/std::initializer_list) or rely on variadic functions of <cstdarg>:
Item::Item(const char* n, bool (*optionChange)(uint8_t), size_t opt_num, ...): name(n), type(MENU_TYPE_OPTS), selectedOpt(0) {
va_list args;
va_start(args, opt_num);
for (decltype(opt_num) i = 0; i < opt_num; ++i) {
const auto option = va_arg(args, const char *);
std::cout << option << std::endl;
}
va_end(args);
...
}
Be advised that in this scenario, you don't have control over types of passed arguments, and the entire contract is supposed to be agreed verbally or in comments. The client code also should provide meta-data for the approach to be viable (i.e. in this example opt_num stores the number of strings passed)
EDIT
CPP community definitely doesn't like the varargs thing, as my answer was instantly downvoted, so another, more C++ friendly solution is to replace varargs with std::initializer_list:
Item::Item(const char* n, bool (*optionChange)(uint8_t), std::initializer_list<const char *> options): name(n), type(MENU_TYPE_OPTS), selectedOpt(0) {
for (const auto& option: options){
std::cout << option << std::endl;
}
...
}

Related

Why I can't pass string literals to const char* const& in this specialized function template

Why pass a string literal to const char* const& in a specialized function template is illegal, while to const char* is legal?
Here's the thing. There are two excercises about template specialization in C++ Primer:
Exercise 16.63: Define a function template to count the number of
occurrences of a given value in a vector. Test your program by passing it a
vector of doubles, a vector of ints, and a vector of strings.
Exercise 16.64: Write a specialized version of the template from the
previous exercise to handle vector<const char*> and a program that
uses this specialization.
The code below is my answer, the compile error appears when I pass the string literal to this specialized count function.
// Count the number of occurrences of a given value in a vector
template <typename T>
std::size_t count(const std::vector<T>& vec, const T& value)
{
std::size_t i = 0;
for (const auto& v : vec) {
if (v == value)
++i;
}
return i;
}
// A specialized version where T = const char*
template <>
std::size_t count(const std::vector<const char*>& vec, const char* const& value)
{
std::size_t i = 0;
for (const auto& v : vec) {
if (!strcmp(v, value))
++i;
}
return i;
}
int main()
{
std::vector<const char*> sVec{ "cpp", "primer", "cpp", "fifth", "edition", "Cpp", "cpp" };
// Error message: no instance of function template "count" matches the argument list,
// argument types are: (std::vector<const char *, std::allocator<const char *>>, const char [4])
std::cout << count(sVec, "cpp") << std::endl;
return 0;
}
Besides, it's perfectly ok to pass a string literal to const char* const& in a nontemplate function, which makes me confused.
void test(const char* const& str)
{
std::cout << str << std::endl;
}
int main()
{
test("cpp"); // Prints "cpp" as expected
return 0;
}
You don't "pass to a function template specialization". The call is deduced against the original template definition (regardless of any specialization), deducing a type if successful. Then if a specialization exists for the deduced type then that will be called.
The error message is about type deduction failing . You should see the exact same error even if you delete the specialization.
The type deduction fails because T occurs in two different parameters but the result of deduction differs for each parameter:
Deducing vector<T>& vec against vector<const char *> produces T=const char *
Deducing const T& value against "cpp" produces T=const char[4] .
Deduction only succeeds if all instances of T being deduced produce the same type. There is no extra step of different types being reconciled by considering available conversions.
One way to solve the problem would be to use overloading instead of specialization (i.e. delete the text template <>). Then there is an overload set consisting of the non-template function, and the result of deduction (if any). If deduction fails , as it does, it is not an error because the overload set still contains something.
An array and a pointer are different types.
It is because of default conversions, that you can pass an array as a pointer parameter.
A template is just that, a "template" for a function.
There is no concrete function to match the parameters that have been specified. So the compiler tries to generate a function from the parameters and can't make a match, so it fails.
But when you specify the template parameter, the compiler knows exactly which function you want, generates it, then tries to call the function using standard rules for calling functions, which allow it to make the conversion.
When you have a template function definition, the requirements for function arguments are stricter.
In this case it will try to match the array and generate the function from the template and Fail. So no function will be available to call.
You can specialize:
// A specialized version where T = const char[ArraySize]
template<std::size_t ArraySize>
std::size_t count(const std::vector<const char*>& vec, const char (&value)[ArraySize])
{
return count<const char*>(vec, value);
}
-- Edit
Or specifically defining a template parameter function, which defines a concrete function to call.
Oh, I changed the code to std::cout << count<const char*>(sVec, "cpp") << std::endl; Then everything is fine. It seems like compiler treats the string literal "cpp" as a const char [4] rather than const char* const&, so I have to explicitly specify the template parameter. Wonder why that happened.

How to explicitly call the specified overload function?

#include <cstdio>
#include <string>
constexpr char str[] = "/home/qspace/etc/client/mmkvcfgsvr_test_byset_cli.conf";
void test(bool a)
{
printf("b=%d",a);
}
void test(const std::string& s){
printf("s=%s",s.c_str());
}
int main()
{
test(str);
return 0;
}
Like this code, the C++ compiler will convert char* to bool and then call the first function, which is inconsistent with my original intention.
Is there any way to prevent the compiler from performing type conversions that I don't want?
Like "-fno-permissive", but unfortunately, it doesn't work.
How to explicitly call the specified overload function?
Convert the argument at call site: test(std::string(str));
Take expected address of overload function: static_cast<void(*)(const std::string&)>(print)(str);
Is there any way to prevent the compiler from performing type conversions that I don't want?
You might add a catch-all overload as deleted: template <typename T> void test(const T&) = delete;
Alternatively, in C++17, you might do the "dispatching" manually:
template <typename T>
void test(const T& t)
{
static_assert(std::is_constructible_v<std::string, T>
|| std::is_convertible_v<T, bool>);
if constexpr (std::is_constructible_v<std::string, T>) {
const std::string& s = t;
printf("s=%s", s.c_str());
} else if constexpr (std::is_convertible_v<T, bool>) {
printf("b=%d", bool(t));
}
}
You're mixing C and STL types (char array vs std::string). There are two solutions. The immediately obvious solution is to create a temporary std::string object every time you wish to pass a char array into a function expecting std::string.
test(std::string(str));
The other solution, which I prefer, is to avoid C types altogether. To create a string constant, use STL type directly:
const std::string str {"/home/qspace/etc/client/mmkvcfgsvr_test_byset_cli.conf"};
If you wish to retain constexpr see this thread: Is it possible to use std::string in a constexpr?

Template specialization unmatched error

Re-edited:
Here's what C++ Primer 5th says:
Version 1:
template <typename T> int compare(const T&, const T&);
Version 2:
template<size_t N, size_t M> int compare(const char (&)[N], const char (&)[M]);
A specialization of Version 1:
template <> int compare(const char* const &p1, const char* const &p2);
For example, we have defined two versions of our compare function template, one that takes references to array parameters and the other that takes const T&. The fact that we also have a specialization for character pointers has no impact on function matching. When we call compare on a string literal: compare("hi", "mom")
both function templates are viable and provide an equally good (i.e., exact) match to the call. However, the version with character array parameters is more specialized (§ 16.3, p. 695) and is chosen for this call.
The book says "both provide an equally good match", so then I thought putting Version 1 and its specialization should compile well. But it didn't.
So "provide an equally good match" doesn't mean it can compile? The book plays a trick on me?
Original code snippet link that I didin't understand why can't compile:
https://wandbox.org/permlink/oSCDWad03nELC9xs
Full context screenshot (I've boxed the most related part, sorry to post such a big pic here).
C-style strings are not pointers, they are arrays. When template type deduction happens, it deduces T as either const char[3] or const char[4]. Since those conflict the compiler is unable to deduce T and it stops there.
template<>
int compare(const char* const &p1, const char* const&p2) {
cout << "const char* const" << endl;
return 3;
}
won't be called because it relies on T being deduced and matching const char* and the compiler was not able to deduce T. A specialization is not a overload, it is a recipe for that specific T. If T can't be deduced then the specialization, even it it were to be a valid overload, won't be called.
If you were to overload the function instead of providing a specialization then it would compile with:
int compare(const char* const &p1, const char* const&p2) {
cout << "const char* const" << endl;
return 3;
}
You are passing to the template function two parameters of different types (the type of "hi" is const char [3] and the type of "mom" is const char [4]), so the compiler is not able to find a T that matches both types.
It's the same error that you would obtain calling std::min(0, 1U); std::min() (one of its overload) expects two arguments of the same type, as your compare() function does.
A possible solution to your problem is to accept parameters of different types:
template <typename T1, typename T2>
int compare(const T1&, const T2&);
This will work without editing the body of your function.
The compiler is unable to match it to one of the existing templates. If you read the section 16.5 carefully you will understand that it would call the second version of the template class.
The function call has 2 different types of parameters const char[3] and const char [4] compiler is unable to find a template specialization that takes 2 different data types as parameters.
The code below is one of the solutions.
#include <iostream>
#include <string>
using namespace std;
template <typename T> int compare(const T&, const T&) {
cout << "const T" << endl;
return 3;
}
template<size_t N, size_t M>
int compare(const char (&p)[N], const char (&q)[M]) {
cout<<p<<" "<<q<<endl;
return 3;
}
int main()
{
compare("hi", "mom");
}
The other solution is as below. It takes 2 different types and access the variables.
#include <iostream>
#include <string>
using namespace std;
template <typename T> int compare(const T&, const T&) {
cout << "const T" << endl;
return 3;
}
template <typename T1, typename T2>
int compare(const T1&p, const T2&q){
cout<<p<<" "<<q<<endl;
return 3;
}
int main()
{
compare("hi", "mom");
}

partial ordering variadic template function clang

I am currently playing on a project using Boost.ProgramOptions and I had to create the following structure to add some constraints on an option:
template <const char *str1, const char*... str2>
struct restrictedValues
{
...
};
In order to validate that new option you must overload the boost::program_options::validate function:
template<class T, class charT>
void validate(boost::any& v, const std::vector< std::basic_string<charT> >& xs, T*, long);
The call for this validate function is the following:
validate(value_store, new_tokens, (T*)0, 0);
As precised by boost: "The target type is specified via a parameter which has the type of pointer to the desired type. This is workaround for compilers without partial template ordering, just like the last 'long/int' parameter."
I, hence, wrote my validate version in the following way:
template<class charT, const char *... str>
void validate(boost::any &v,
const std::vector<std::basic_string<charT> >& values,
restrictedValues<str...>* /*target_type*/,
int /*unused*/) { ... }
It looks like my version of clang (Ubuntu clang version 3.5.0-4ubuntu2~trusty2 (tags/RELEASE_350/final) (based on LLVM 3.5.0)) simply skip my version and gracefully fail in the default version. Whilst my gcc ((Ubuntu 4.8.2-19ubuntu1) 4.8.2) happily compile.
EDIT See a live example that shows the different behaviour, credits #dyp:
Live On Coliru
#include <boost/any.hpp>
#include <vector>
#include <string>
#include <iostream>
template <const char *str1, const char*... str2> struct restrictedValues
{
/*...*/
};
template<class T, class charT>
void validate(boost::any&, const std::vector< std::basic_string<charT> >&, T*, long)
{
std::cout << "default version\n";
}
extern char const client[] = "hello";
extern char const server[] = "world";
template<class charT, const char *... str>
void validate(boost::any &,
const std::vector<std::basic_string<charT> >&,
restrictedValues<str...>* /*target_type*/,
int /*unused*/) {
std::cout << "custom version\n";
}
int main()
{
boost::any a;
std::vector<std::string> xs;
restrictedValues<client, server>* p = 0;
validate(a, xs, p, 0);
}
Moreover the same process using non-variadic templates (a fixed number of const char*) for the structure/function does work like a charm.
I am not quite sure which lookup process leads to such a ambiguous error.
If my function didn't use template, it would be selected according to the overloading rules, but that isn't the case. By reading the partial ordering rules for template functions, both functions have the same specialization for the template parameters, but my expectation would be that the int/long trick should work. Any idea on how to solve this template mystery?
The usual approach here is to make ADL work using strong typedefs.
I've documented this in an older answer¹:
Custom validator for boost program_options doesn't work with GCC, works with MSVC
¹ the first comments there are obsolete and refer to the old answer I had before.

C++ String-type independent algorithms

I'm trying to derive a technique for writing string-algorithms that is truly independent of the underlying type of string.
Background: the prototypes for GetIndexOf and FindOneOf are either overloaded or templated variations on:
int GetIndexOf(const char * pszInner, const char * pszString);
const char * FindOneOf(const char * pszString, const char * pszSetOfChars);
This issue comes up in the following template function:
// return index of, or -1, the first occurrence of any given char in target
template <typename T>
inline int FindIndexOfOneOf(const T * str, const T * pszSearchChars)
{
return GetIndexOf(FindOneOf(str, pszSearchChars), str);
}
Objectives:
1. I would like this code to work for CStringT<>, const char *, const wchar_t * (and should be trivial to extend to std::string)
2. I don't want to pass anything by copy (only by const & or const *)
In an attempt to solve these two objectives, I thought I might be able to use a type-selector of sorts to derive the correct interfaces on the fly:
namespace details {
template <typename T>
struct char_type_of
{
// typedef T type; error for invalid types (i.e. anything for which there is not a specialization)
};
template <>
struct char_type_of<const char *>
{
typedef char type;
};
template <>
struct char_type_of<const wchar_t *>
{
typedef wchar_t type;
};
template <>
struct char_type_of<CStringA>
{
typedef CStringA::XCHAR type;
};
template <>
struct char_type_of<CStringW>
{
typedef CStringW::XCHAR type;
};
}
#define CHARTYPEOF(T) typename details::char_type_of<T>::type
Which allows:
template <typename T>
inline int FindIndexOfOneOf(T str, const CHARTYPEOF(T) * pszSearchChars)
{
return GetIndexOf(FindOneOf(str, pszSearchChars), str);
}
This should guarantee that the second argument is passed as const *, and should not determine T (rather only the first argument should determine T).
But the problem with this approach is that T, when str is a CStringT<>, is a copy of the CStringT<> rather than a reference to it: hence we have an unnecessary copy.
Trying to rewrite the above as:
template <typename T>
inline int FindIndexOfOneOf(T & str, const CHARTYPEOF(T) * pszSearchChars)
{
return GetIndexOf(FindOneOf(str, pszSearchChars), str);
}
Makes it impossible for the compiler (VS2008) to generate a correct instance of FindIndexOfOneOf<> for:
FindIndexOfOneOf(_T("abc"), _T("def"));
error C2893: Failed to specialize function template 'int FindIndexOfOneOf(T &,const details::char_type_of<T>::type *)'
With the following template arguments: 'const char [4]'
This is a generic problem I've had with templates since they were introduced (yes, I'm that old): That it's been essentially impossible to construct a way to handle both old C-style arrays and newer class based entities (perhaps best highlighted by const char [4] vs. CString<> &).
The STL/std library "solved" this issue (if one can really call it solving) by instead using pairs of iterators everywhere instead of a reference to the thing itself. I could go this route, except it sucks IMO, and I don't want to have to litter my code with two-arguments everywhere a single argument properly handled should have been.
Basically, I'm interested in an approach - such as using some sort of stringy_traits - that would allow me to write GetIndexOfOneOf<> (and other similar template functions) where the argument is the string (not a pair of (being, end] arguments), and the template that is then generated be correct based on that string-argument-type (either const * or const CString<> &).
So the Question: How might I write FindIndexOfOneOf<> such that its arguments can be any of the following without ever creating a copy of the underlying arguments:
1. FindIndexOfOneOf(_T("abc"), _T("def"));
2. CString str; FindIndexOfOneOf(str, _T("def"));
3. CString str; FindIndexOfOneOf(T("abc"), str);
3. CString str; FindIndexOfOneOf(str, str);
Related threads to this one that have lead me to this point:
A better way to declare a char-type appropriate CString<>
Templated string literals
Try this.
#include <type_traits>
inline int FindIndexOfOneOf(T& str, const typename char_type_of<typename std::decay<T>::type>::type* pszSearchChars)
The problem is that when you make the first argument a reference type T becomes deduced as:
const char []
but you want
const char*
You can use the following to make this conversion.
std::decay<T>::type
The documentation says.
If is_array<U>::value is true, the modified-type type is remove_extent<U>::type *.
You can use Boost's enable_if and type_traits for this:
#include <boost/type_traits.hpp>
#include <boost/utility/enable_if.hpp>
// Just for convenience
using boost::enable_if;
using boost::disable_if;
using boost::is_same;
// Version for C strings takes param #1 by value
template <typename T>
inline typename enable_if<is_same<T, const char*>, int>::type
FindIndexOfOneOf(T str, const CHARTYPEOF(T) * pszSearchChars)
{
return GetIndexOf(FindOneOf(str, pszSearchChars), str);
}
// Version for other types takes param #1 by ref
template <typename T>
inline typename disable_if<is_same<T, const char*>, int>::type
FindIndexOfOneOf(T& str, const CHARTYPEOF(T) * pszSearchChars)
{
return GetIndexOf(FindOneOf(str, pszSearchChars), str);
}
You should probably expand the first case to handle both char and wchar_t strings, which you can do using or_ from Boost's MPL library.
I would also recommend making the version that takes a reference take a const reference instead. This just avoids instantiation of 2 separate versions of the code (as it stands, T will be inferred as a const type for const objects, and a non-const type for non-const objects; changing the parameter type to T const& str means T will always be inferred as a non-const type).
Based on your comments about iterators it seems you've not fully considered options you may have. I can't do anything about personal preference, but then again...IMHO it shouldn't be a formidable obstacle to overcome in order to accept a reasonable solution, which should be weighed and balanced technically.
template < typename Iter >
void my_iter_fun(Iter start, Iter end)
{
...
}
template < typename T >
void my_string_interface(T str)
{
my_iter_fun(str.begin(), str.end());
}
template < typename T >
void my_string_interface(T* chars)
{
my_iter_fun(chars, chars + strlen(chars));
}
Alternative to my previous answer, if you don't want to install tr1.
Add the following template specializations to cover the deduced T type when the first argument is a reference.
template<unsigned int N>
struct char_type_of<const wchar_t[N]>
{
typedef wchar_t type;
};
template<unsigned int N>
struct char_type_of<const char[N]>
{
typedef char type;
};