Function template argument deduction with user-defined conversion operator - c++

Let's say I have a class that wraps a string literal:
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
Now, I'd like for instances of this wrapped string type to be convertible back to a const char[N] implicitly, in a way I can still access its size. I'd like to be able to do something like:
template <size_t N>
void foo(const char(&s)[N]) {
std::cout << N << ": " << s << std::endl;
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
}
My goal is to have one function defined for foo() that can accept actual string literals as well as wrapped literals. I've tried adding a user-defined conversion operator to the class definition:
using arr_t = char[N+1];
constexpr operator const arr_t&() const { return wrapped_; }
But this gives me the following with clang:
candidate template ignored: could not match 'const char [N]' against 'const literal<7>'
If I change the call to foo() to the following, it works:
foo((const char(&)[8])s);
...which means that the conversion operator works, but not in the context of template argument deduction. Is there any way I can make this work without defining foo() specifically to take a wrapped literal?

The problem you are having is templates never do conversions of the parameters. Since you give it a const literal<7>, that is all it has to work with.
The easy fix for this is to add an overload and then do the cast in the overload to call your string literal version. That should look like
template <size_t N>
void foo(const literal<N> &lit) {
foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
}
which gives you a full example of
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
using arr_t = const char[N+1]; // <- Add const here since literals are const char[N]
constexpr operator const arr_t&() const { return wrapped_; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
template <size_t N>
void foo(const char(&s)[N]) {
std::cout << N << ": " << s << std::endl;
}
template <size_t N>
void foo(const literal<N> &lit) {
foo(static_cast<typename literal<N>::arr_t&>(lit)); // explicitly cast to the array type alias
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
}
Yes, you are adding an overload but all of the important code does not have to be duplicated.
If you can use C++17 and you don't mind a little indirection you could do all of this with one function using a std::string_view and providing literal with a operator std::string_view. That would look like
template <size_t N>
class literal {
public:
constexpr literal(const char(&s)[N+1]) : wrapped_(s) {}
constexpr const char * c_str() const { return wrapped_; }
constexpr size_t size() const { return N; }
using arr_t = const char[N+1];
constexpr operator std::string_view() const { return wrapped_; }
private:
const char (&wrapped_)[N+1];
};
template <size_t N>
constexpr literal<N-1> make_literal(const char (&s)[N]) { return literal<N-1>(s); }
void foo(std::string_view s) {
std::cout << s.size() << ": " << s << std::endl;
}
int main() {
constexpr auto s = make_literal("testing");
foo(s);
foo("testing");
}

Related

How to use proxy class and template to "simulate" function overload by return value?

I'll trying to dig into more c++ template and type system knowledge, I know that by overloading Type.Operator(), it will seem to "overload" by return type,
#include<iostream>
using namespace std;
class My{
int getInt() const {return 20;}
short getShort() const {return 3;}
public:
template<class T>
T get() const
template<int>
int get() const {return getInt();}
template<short>
short get() const {return getShort();}
};
struct Proxy{
My const* myOwner;
Proxy(My const* owner):myOwner(owner){}
operator int() const {return myOwner->getInt();}
operator short() const {return myOwner->getShort();}
};
int main(){
My m;
Proxy p(&m);
int _i = p;//works!
short _s = p;//works!
cout<<_i<<","<<_s<<",\n";
// How to use template My::get
int i = m.get(); // doesn't compile
short s = m.get(); // doesn't compile
cout<<i<<","<<s<<",\n";
return 0;
}
I expect there should be some way to make the following lines work to differ function call according to return type:
int i = m.get();
short s = m.get();
How to implement this? Thanks a lot.
... to "simulate" function overload by return value?
Ignoring all the boilerplate of your example and focusing solely on this question, you could define a primary template function with a deleted (primary template) definition, and add explicit (full) specializations of this function only for the types for which you want to have an "overload":
#include <cstdint>
template <typename T>
T get() = delete;
template<>
uint8_t get<uint8_t>() { return 42U; } // or delegate to getUint8().
template<>
uint32_t get<uint32_t>() { return 43U; }
template<>
float get<float>() { return 44.5F; }
int main() {
const auto a = get<uint8_t>();
const auto b = get<uint32_t>();
const auto c = get<float>();
// const auto d = get<uint16_t>(); // error: use of deleted function
}
Note however that you need to explicitly specify the type of the single type template parameter, as there is no argument of get() for template argument deduction to work with.
If I understand your goal, what's missing in your implementation (among other necessary fixes) is member function of My which actually returns a Proxy.
Something like the following (testable here)
#include<iostream>
class Proxy;
class My
{
public:
template<class T = Proxy> // <--
T get() const;
};
class Proxy
{
My const& owner_;
public:
Proxy(My const& owner) : owner_(owner)
{}
operator int() const;
operator short() const;
};
template<>
Proxy My::get<Proxy>() const {
return *this; // <---
}
template<>
int My::get<int>() const {
return 20;
}
template<>
short My::get<short>() const {
return -3;
}
Proxy::operator int() const {
return owner_.get<int>();
}
Proxy::operator short() const {
return owner_.get<short>();
}
int main()
{
My m;
int i = m.get<int>();
short s = m.get<short>();
std::cout << i << ", " << s << '\n';
int i_ = m.get();
short s_ = m.get();
std::cout << i_ << ", " << s_ << '\n';
}
You can use SFINAE like:
template <typename T,
typename std::enable_if<std::is_same<T, int>::value>::type* = nullptr>
int get() const {
return getInt();
}
template <typename T,
typename std::enable_if<std::is_same<T, short>::value>::type* = nullptr>
short get() const {
return getShort();
}
Then in Proxy class call the above functions like:
operator int() const {
return myOwner->get<int>();
}
operator short() const {
return myOwner->get<short>();
}
and in main function:
int i = m.get<int>();
short s = m.get<short>();
Demo
NOTE: The fact that the function return value is being assigned to an int or short isn't used in template parameter deduction.

Stringify template parameters

I am trying to extend the code from the answer to this question Unpack parameter pack into string view.
Instead of stringifying char-only types, I would like to use a constexpr_string type, custom written:
#include <array>
#include <iostream>
class constexpr_string {
public:
template <std::size_t N>
constexpr constexpr_string(const char (&s)[N]) : string_{s}, size_{N - 1} {}
constexpr constexpr_string() = default;
constexpr char const operator[](std::size_t n) const { return string_[n]; }
constexpr std::size_t GetSize() { return size_; }
private:
char const *string_{nullptr};
std::size_t size_{0};
};
template <constexpr_string... strings> constexpr auto stringify() {
std::array<constexpr_string, sizeof...(strings)> array = {strings...};
return array;
}
int main() {
static constexpr auto s = stringify<"abc", "def", "fgh">();
for (auto &i : s) {
std::cout << i << std::endl;
}
return 0;
}
When I compile though, I get :
main.cpp:18:31: error: ‘class constexpr_string’ is not a valid type for a template non-type parameter
template constexpr auto stringify()
Is such a thing even possible? I'm compiling with g++ (Ubuntu 7.3.0-27ubuntu1~18.04) 7.3.0. Many thanks in advance!

Function template specialization type - is it optional?

Is the <const char*> optional in below code? I found that g++ and clang compiles without it just fine.
template<typename T>
void debugRep2(T const& t) {
std::cout << "debugRep(const T& t)\n";
}
template<>
void debugRep2<const char*>(const char* const& t) {
//^^^^^^^^^^^^^
std::cout << "const char*& t\n";
}
int main() {
int n;
int *pn = &n;
debugRep2(n);
debugRep2(pn);
}
The templated type is already specified at the function parameter and can be deduced by the compiler
template<>
void debugRep2<const char*>(const char* const& t) {
// ^^^^^^^^^^^ already present
// ...
}
So yes, in this case it is optional.
In fact the common way to write that specialization would be
template<>
void debugRep2(const char* const& t) {
// ...
}

array decay to pointer and overload resolution

I want to be able to differentiate array from pointers in overload resolution :
class string {
public:
string(const char* c_str);
template<int N>
string(const char (&str) [N]);
};
int main() {
const char* c_str = "foo";
string foo(c_str); // ok will call string(const char*)
string bar("bar"); // call string(const char*) instead of the array version
}
The best I have found so far is to use a reference to the pointer instead of a pointer :
class string {
public:
string(const char*& c_str);
template<int N>
string(const char (&str) [N]);
};
int main() {
const char* c_str = "foo";
string foo(c_str); // ok will call string(const char*)
string bar("bar"); // ok, will call the array version
}
it's not exactly the same thing and I want to know if a better way exist
You need to make the first overload a poorer choice when both are viable. Currently they are a tie on conversion ranking (both are "Exact Match"), and then the tie is broken because non-templates are preferred.
This ought to make the conversion ranking worse:
struct stg
{
struct cvt { const char* p; cvt(const char* p_p) : p(p_p) {} };
// matches const char*, but disfavored in overload ranking
stg(cvt c_str); // use c_str.p inside :( Or add an implicit conversion
template<int N>
stg(const char (&str) [N]);
};
You can use SFINAE. This might not be the best way, but it should work ok:
//thanks to dyp for further reduction
template<typename T, typename = typename std::enable_if<std::is_same<T, char>::value>::type>
string(const T * const &) {std::cout << "const char *\n";}
template<std::size_t N> //credit to jrok for noticing the unnecessary SFINAE
string(const char(&)[N]) {std::cout << "const char(&)[" << N << "]\n";}
Here's a live example.
A more generic version of this problem could be detected as follows.
template <class T>
void func(T,
typename std::enable_if<std::is_pointer<T>::value, void>::type * = 0)
{
// catch ptr
}
template <class T, int N>
void func(T (&)[N])
{
//catch array
}
int main(void)
{
int arr[5];
char *b = 0;
func(arr); // catch array
func(b); // catch ptr
}

const char array (c style string) template specialization

I want to be able to specialize based on a constant c style string. The problem is that when I call my templatized function the type is const char[N] where 'N' is the size of the string +1 (null character). How can I specialize for all c-style strings?
The following code displays the problem. You can see the specialization for const char [15] gets matched for "const char [15]" but for "const char[5]" it goes to Generic.
Is there any way to do this?
template <typename T>
struct Test {
static const char* type() { return "Generic"; }
};
template <>
struct Test<const char*> {
static const char* type() { return "const char*"; }
};
template <>
struct Test<const char[]> {
static const char* type() { return "const char[]"; }
};
template <>
struct Test<const char[15]> {
static const char* type() { return "const char[15]"; }
};
template <>
struct Test<char*> {
static const char* type() { return "char*"; }
};
template <>
struct Test<char[]> {
static const char* type() { return "char[]"; }
};
template <typename T>
void PrintType(const T& expected) {
std::cerr << expected << " type " << Test<T>::type() << std::endl;
}
int main(int argc, char* argv[]) {
const char* tmp = "const char*";
PrintType(tmp);
PrintType("const char[]");
PrintType("const char[15]");
PrintType("const char[5]");
}
output when run in Windows 7 - VS 2008
const char* type const char*
const char[] type Generic
const char[15] type const char[15]
const char[5] type Generic
A specialization for any array of chars is:
template< std::size_t N >
struct Test< const char[N] > { ... };
However you can no longer return a char* from type(), unless you write more metafunctions to turn a non-type template parameter into its textual representation.
Try this:
#include <cstdio>
#include <string>
template<class T>
void f2(const T& s) // Handle all kinds of string objects
{ std::printf("string object: %s\n", s.c_str()); }
void f2(const char* s) // Handle const char*
{ std::printf("const char*: %s\n", s); }
// ----------------------------------------------------------------------------
template<size_t N>
void f(const char(&s)[N]) // Handle const char array
{ std::printf("const char[%zu]: %s\n", N, s); }
template<size_t N>
void f(char(&s)[N]) // Handle char array
{ std::printf("char[%zu]: %s\n", N, s); }
template<class T>
inline void f(T&& s) // Handle other cases
{ f2(std::forward<T>(s)); }
int main() {
std::string stringObj = "some kind of string object ...";
char charArr[] = "char array";
const char constCharArr[] = "const char array";
const char* constCharPtr = "const char pointer";
f(stringObj);
f(charArr);
f(constCharArr);
f(constCharPtr);
//f("const char array");
}
Output:
string object: some kind of string object ...
char[11]: char array
const char[17]: const char array
const char*: const char pointer
Explanation
f() has two kinds of overloads: One for char arrays and one for "everything else".
f2() handles the "everything else" case.