How to explicitly call the specified overload function? - c++

#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?

Related

Strongly typed c++ varargs [duplicate]

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;
}
...
}

How do I force conversion char[] to char* in template instantiation?

Let's say I have a function:
#include <optional>
template <typename T>
std::optional<T> foo(T const &input);
It accepts a value, attempts to work with a copy of it and returns said copy on success (std::nullopt on fail).
But the problem is, when a string literal is passed into such function, an error T in optional<T> must meet the Cpp17Destructible requirements occurs.
It's caused by static_assert(is_object_v<_Ty> && is_destructible_v<_Ty> && !is_array_v<_Ty>, ...) defined in <optional>.
The next expression works correctly:
foo((char const*) "bar");
This one fails:
foo("bar");
The question is, how do I force the compiler to implicitly convert char const[] to char const*?
P. S. I know that it could be done by simply overloading the function, by I'm not too keen on code duplication it causes, so I'm curious whether an alternative solution is applicable here.
Edit: Rewrote the answer. With return type deduction, this would be convenient.
template <typename T>
auto foo(T const &input){
auto copy {std::move(input)};
// ...
return std::optional{std::move(copy)};
}
Not really what you asked for, though consider that not much repetition is needed:
template <int s>
std::optional<const char*> foo(const char (&str)[s]) {
return foo(&str[0]);
}
or simpler:
std::optional<char const*> foo(char const *input) {
return foo<char const *>(input);
}

Deducing a const l-value reference from a non-const l-value reference in C++ template

Suppose you have the following pair of functions:
void f(const int&) {
// Do something, making a copy of the argument.
}
void f(int&&) {
// Do the same thing, but moving the argument.
}
They are fairly redundant—the only difference between the functions being whether they copy or move their argument. Of course, we can do better by re-writing this as a single template function:
template<typename T>
void g(T&&) {
// Do something, possibly using std::forward to copy or move the argument.
}
This works, and is a commonly used idiom in practice. But the template might be instantiated into three functions, up from our two above. We can verify this occurs with the following piece of code:
#include <iostream>
template<typename T> constexpr char *type = nullptr;
template<> constexpr const char *type<int&> = "int&";
template<> constexpr const char *type<const int&> = "const int&";
template<> constexpr const char *type<int> = "int";
template<typename T>
void g(T&&) {
std::cout << reinterpret_cast<void*>(&g<T>)
<< " = &g<" << type<T> << ">" << std::endl;
}
int main() {
int i = 0;
const int& cr = 0;
g(i);
g(cr);
g(0);
return 0;
}
/*
Prints:
0x100f45080 = &g<int&>
0x100f45100 = &g<const int&>
0x100f45180 = &g<int>
*/
This has added a third function for the case when T = int&, which we didn't have when we were using our non-templated function f above. In this case, we don't actually need this non-const l-value reference version of the function—given f was sufficient for our original needs—and this increases the size of our code, especially if we have many template functions written this way that call each other.
Is there a way to write our function g above so that the compiler will automatically deduce T = const int& when g(i) is called in our example code? I.e., a way where we don't have to manually write g<const int&>(i) yet still get the desired behavior.
It is a subjective point-of-view to say "forward references" ("universal references") are better than dedicated overloads. There are certainly many cases where this is true, but if you want to have full control they won't do all the jobs.
You could explicitly make sure users do not pass non-const lvalue references, by adding
static_assert(!std::is_lvalue_reference<T>::value || std::is_const<typename std::remove_reference<T>::type>::value, "only call g with const argument");
inside g, but this is not in all cases a very good solution.
Or you do what is done for vector::push_back(...) and provide explicit overloads -- but this was your starting point, see https://en.cppreference.com/w/cpp/container/vector/push_back.
The 'correct' answer just depends on your requirements.
Edit:
the suggestion of #Sjoerd would look something like:
template <typename T>
class aBitComplicated {
public:
void func(T&& v) { internal_func(std::forward<T>(v)); }
void func(const T& v) { internal_func(v); }
private:
template <typename U>
void internal_func(U&& v) { /* your universal code*/ }
};
There also a bit more sophisticated/complicated version of this, but this here should be the most simple version to achieve what you asked for.

Using template to handle string and wstring

I have following two functions:
void bar(const std::string &s)
{
someCFunctionU(s.c_str());
}
void bar(const std::wstring &s)
{
someCFunctionW(s.c_str());
}
Both of these call some C function which accepts const char * or const wchar_t * and have U or W suffixes respectively. I would like to create a template function to handle both of these cases. I tried following attempt:
template <typename T>
void foo(const std::basic_string<T> &s)
{
if constexpr (std::is_same_v<T, char>)
someCFunctionU(s.c_str());
else
someCFunctionW(s.c_str());
}
But this does not seem to work correctly. If I call:
foo("abc");
this will not compile. Why is that? why a compiler is not able to deduce the proper type T to char? Is it possible to create one function which would handle both std::string and std::wstring?
this will not compile. Why is that? why a compiler is not able to deduce the proper type T to char?
As better explained by others, "abc" is a char[4], so is convertible to a std::basic_string<char> but isn't a std::basic_string<char>, so can't be deduced the T type as char for a template function that accept a std::basic_string<T>.
Is it possible to create one function which would handle both std::string and std::wstring?
Yes, it's possible; but what's wrong with your two-function-in-overloading solution?
Anyway, if you really want a single function and if you accept to write a lot of casuistry, I suppose you can write something as follows
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_same_v<T, std::string> )
someCFunctionU(s.c_str());
else if constexpr ( std::is_convertible_v<T, char const *>)
someCFunctionU(s);
else if constexpr ( std::is_same_v<T, std::wstring> )
someCFunctionW(s.c_str());
else if constexpr ( std::is_convertible_v<T, wchar_t const *> )
someCFunctionW(s);
// else exception ?
}
or, a little more synthetic but less efficient
template <typename T>
void foo (T const & s)
{
if constexpr ( std::is_convertible_v<T, std::string> )
someCFunctionU(std::string{s}.c_str());
else if constexpr (std::is_convertible_v<T, std::wstring> )
someCFunctionW(std::wstring{s}.c_str());
// else exception ?
}
So you should be able to call foo() with std::string, std::wstring, char *, wchar_t *, char[] or wchar_t[].
The issue here is that in foo("abc");, "abc" is not a std::string or a std::wstring, it is a const char[N]. Since it isn't a std::string or a std::wstring the compiler cannot deduce what T should be and it fails to compile. The easiest solution is to use what you already have. The overloads will be considered and it is a better match to convert "abc" to a std::string so it will call that version of the function.
If you want you could use a std::string_view/std::wstring_view instead of std::string/std::wstring so you don't actually allocate any memory if you pass the function a string literal. That would change the overloads to
void bar(std::string_view s)
{
someCFunctionU(s.data());
}
void bar(std::wstring_view s)
{
someCFunctionW(s.data());
}
Do note that std::basic_string_view can be constructed without having a null terminator so it is possible to pass a std::basic_string_view that won't fulfill the null terminated c-string requirement that your C function has. In that case the code has undefined behavior.
A workaround in C++17 is:
template <typename T>
void foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
And to avoid issue mentioned by Justin about non-null-terminated string
template <typename T> struct is_basic_string_view : std::false_type {};
template <typename T> struct is_basic_string_view<basic_string_view<T>> : std::true_type
{};
template <typename T>
std::enable_if_t<!is_basic_string_view<T>::value> foo(const T &s)
{
std::basic_string_view sv{s}; // Class template argument deduction
if constexpr (std::is_same_v<typename decltype(sv)::value_type, char>)
someCFunctionU(sv.data());
else
someCFunctionW(sv.data());
}
Yes, there exist a type, i.e. std::basic_string<char>, which can be copy initialized from expression "abc". So you can call a function like void foo(std::basic_string<char>) with argument "abc".
And no, you can't call a function template template <class T> void foo(const std::basic_string<T> &s) with argument "abc". Because in order to figure out whether the parameter can be initialized by the argument, the compiler need to determine the template parameter T first. It will try to match const std::basic_string<T> & against const char [4]. And it will fail.
The reason why it will fail is because of the template argument deduction rule. The actual rule is very complicated. But in this case, for std::basic_string<char> to be examined during the deduction, compiler will need to look for a proper "converting constructor", i.e. the constructor which can be called implicitly with argument "abc", and such lookup isn't allowed by the standard during deduction.
Yes, it is possible to handle std::string and std::wstring in one function template:
void foo_impl(const std::string &) {}
void foo_impl(const std::wstring &) {}
template <class T>
auto foo(T &&t) {
return foo_impl(std::forward<T>(t));
}

Could not deduce template argument with std::basic_string

The striped-down version of my problem:
I want to merge these two functions:
void Bar(const std::string &s);
void Bar(const std::wstring &s);
..into one templated function:
template <class CharType>
void Foo(const std::basic_string<CharType> &s);
And I thought I will be be able to call Foo like (1) and (2), but to my surprise not even (3) works.
(1) Foo("my string");
(2) Foo(std::string("my string"));
(3) Foo(std::basic_string<char>("my string"));
I tried removing the const qualifier for parameter s and even dropping the reference (&), or calling with lvalues instead of rvalues, but all with the same result.
The compiler (both gcc and VS - so I am pretty sure it's a standard compliant behaviour) can't deduce the template argument for Foo. Of course it works if I call Foo like Foo<char>(...).
So I would like to understand why this is, especially since the call (3) is a one-to-one type between the type of the calling parameter object and the function argument type.
Secondly, I would like a workaround to this: to be able to use one templated function, and to be able to call it like (1) and (2).
Edit
(2) and (3) do work. I was declaring it wrong in my compiler (not like in my question):
template <class CharType>
void Foo(const std::basic_string<char> &s);
Sorry about that.
1) won't work because you're trying to use a const char[10] instead of a std::string
2) should work and so should 3) since default template parameters should ensure you're using defaults
#include <iostream>
using namespace std;
template <class CharType>
void Foo(const std::basic_string<CharType> &s)
{
cout << s.c_str(); // TODO: Handle cout for wstring!!!
}
void Foo(const char *s)
{
Foo((std::string)s);
}
int main()
{
std::wstring mystr(L"hello");
Foo(mystr);
Foo("world");
Foo(std::string("Im"));
Foo(std::basic_string<char>("so happy"));
return 0;
}
http://ideone.com/L63Gkn
Careful when dealing with template parameters. I also provided a small overload for wstring, see if that fits you.