1

I want to get three different functions for calls like this:

foo("aa");

and like this one:

char buf[2];
foo(buf);

and one more variant for calls like this one:

const char *ptr;
//set ptr
foo(ptr);//don't know buffer size at compile time

I try this:

//and foo1 for pointers, but how???

template<size_t N>
void foo1(char (&)[N], std::false_type)
{
  std::printf("not const\n");
}

template<size_t N>
void foo1(const char (&)[N], std::true_type)
{
   std::printf("const\n");
}

template<typename arr_t>
void foo(arr_t arr) 
{
  foo1(std::forward<arr_t>(arr), std::is_const<typename std::remove_reference<arr_t>::type>{});
}

foo("a");

But it fails to compile, looks like "a" converted to const char * not to const char (&)[2],

but interesting that such code compiles fine:

template<size_t N>
void f(const char (&)[N])
{
}

f("aaaa");

so how can I overload function between constant at compilation time (and know size of this constant at compile time) and array with known size, but not const?

too honest for this site
  • 12,050
  • 4
  • 30
  • 52
user1244932
  • 7,352
  • 5
  • 46
  • 103

5 Answers5

4

There are two issues here. Firstly, your dispatching function is taking its argument by value. To do perfect forwarding you should take your argument by forwarding reference.

The second issue, which surprised me, is that pointer decay takes priority over a deduced template so a function taking a pointer will be called in preference to a template taking an array.

You could tag dispatch using the std::is_array type trait.

template<size_t N>
void foo1(char (&)[N], std::true_type)
{
  std::cout << "array\n";
}

template<size_t N>
void foo1(const char (&)[N], std::true_type)
{
   std::printf("const array\n");
}

void foo1(const char*, std::false_type)
{
   std::cout << "pointer\n";
}

template<typename T>
void foo(T&& x)
{
  foo1(std::forward<T>(x), std::is_array< typename std::remove_reference<T>::type >());
}

Live demo.

Community
  • 1
  • 1
Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • Templates have no trouble deducing an array type from a string literal, so long as the parameter isn't declared such that the function would be taking an array by value. I.e, the parameter has to be a reference type. The OP's code works just fine with no changes except to fix the type of parameter he's trying to perfect-forward. – bames53 Sep 11 '15 at 01:35
  • @bames53 No, [OP's code won't work fine](http://melpon.org/wandbox/permlink/c9mPjn3xw552BMGW) even if you fix the perfect forward. At least not for what they asked to do. – Chris Drew Sep 11 '15 at 02:03
  • [Here's](http://coliru.stacked-crooked.com/a/156509d34dcf85fc) the OP's code, with the corrected perfect forwarding, and with an overload that supports `char const *` as requested. – bames53 Sep 11 '15 at 04:57
  • @bames53 Sure, you have found a different workaround for the problem with that `struct`. – Chris Drew Sep 11 '15 at 07:15
1

so how can I overload function between constant at compilation time (and know size of this constant at compile time) and array with known size, but not const?

With one change, your code is perfectly fine and does exactly what you want. The problem is that you're not doing perfect forwarding correctly:

template<typename arr_t>
void foo(arr_t arr) 
{
  foo1(std::forward<arr_t>(arr), std::is_const<typename std::remove_reference<arr_t>::type>{});

Perfect forwarding requires the use of a forwarding reference:

template<typename arr_t>
void foo(arr_t &&arr) 
{
  foo1(std::forward<arr_t>(arr), std::is_const<typename std::remove_reference<arr_t>::type>{});

Now when you pass a string literal arr_t will deduce to const char (&)[N], and the appropriate foo1 function will be called.

Of course const is also deduced and there's no need for the second parameter to foo1().

and one more variant for calls like this one:

For this add an overload that matches char const *, but which imposes an implicit conversion to make it a worse match than char const (&).

Live


However if you're just wanting to overload between these three types, you don't need to mess with the perfect forwarding bit at all:

template<size_t N>
void foo(char (&)[N])
{
  std::printf("not const[]\n");
}

template<size_t N>
void foo(const char (&)[N])
{
   std::printf("const[]\n");
}

template<typename T>
struct dumb_ptr { T *p; dumb_ptr(T *p_) : p(p_) {} };

void foo(dumb_ptr<char const>) {
   std::printf("ptr\n");    
}

int main() {

foo("a");           // prints const []

char aa[] = "aa";
foo(aa);            // prints non const []

char const *aaa;
foo(aaa);           // prints ptr

char *aaaa;
foo(aaaa);          // prints ptr

}
bames53
  • 86,085
  • 15
  • 179
  • 244
  • But OP wants to distinguish a third type as well, `const char *ptr;`. – Chris Drew Sep 11 '15 at 02:01
  • @ChrisDrew The OPs `foo` can work as-is for `const char *`. I've added an overload in my example to show it working. – bames53 Sep 11 '15 at 04:30
  • @ChrisDrew [Here's](http://coliru.stacked-crooked.com/a/156509d34dcf85fc) the example with the OPs `true_type`/`false_type` stuff left in. – bames53 Sep 11 '15 at 04:55
1
  • C++11
  • msvc2015u3,gcc5.4,clang3.8.0

I've used a bit another approach with template specialization to equalize deduction preorities between not template function with const char * argument and template function with const char (&)[S] argument, plus string character type tagging together with T &&:

    #include <string>
    #include <cstdio>
    #include <type_traits>

    struct tag_string{};
    struct tag_wstring{};

    template <typename t_elem, typename t_traits, typename t_alloc>
    inline constexpr const t_elem * get_c_str(const std::basic_string<t_elem, t_traits, t_alloc> & str)
    {
        return str.c_str();
    }

    template <typename CharT>
    inline constexpr CharT * get_c_str(CharT * str)
    {
        return str;
    }

    namespace detail {

        template <typename T>
        void _foo(T && s, tag_string)
        {
            // to check on convertion from string type
            static_assert(std::is_convertible<T, std::string>::value, "must be convertible to a std::string type");

            // implementation for char
            printf("%s: _foo(T &&, tag_string)\n\n", get_c_str(s));
        }

        template <typename T>
        void _foo(T && s, tag_wstring)
        {
            // to check on convertion from string type
            static_assert(std::is_convertible<T, std::wstring>::value, "must be convertible to a std::wstring type");

            // implementation for wchar_t
            printf("%ls: _foo(T &&, tag_wstring)\n\n", get_c_str(s));
        }
    }

    // for rvalues
    void foo(std::string && s)
    {
        puts("foo(std::string &&)");
        detail::_foo(s, tag_string{});
    }

    // for lvalues
    void foo(const std::string & s)
    {
        puts("foo(const std::string &)");
        detail::_foo(s, tag_string{});
    }

    // for lvalue/rvalue character arrays with compile time size
    template <size_t S>
    void foo(const char (& s)[S])
    {
        puts("foo(const char (&)[])");
        detail::_foo(s, tag_string{});
    }

    // for character pointers w/o compile time size (can be with second parameter of string runtime size)
    template <typename T>
    void foo(const T * const & s);

    // through template specialization to equalize priorities over function overloading deduction
    template<>
    void foo(const char * const & s)
    {
        puts("foo(const char * const &)");
        detail::_foo(s, tag_string{});
    }

'

    int main()
    {
        foo("111");

        char a[] = "222";
        foo(a);

        const char a2[] = "333";
        foo(a2);

        char * b = a;
        foo(b);

        const char * b2 = "555";
        foo(b2);

        foo({'6', '6', '6', '\0'});

        foo(std::string{"777"});

        std::string s = "888";
        foo(s);
    }

Output:

    foo(const char (&)[])
    111: _foo(T &&, tag_string)

    foo(const char (&)[])
    222: _foo(T &&, tag_string)

    foo(const char (&)[])
    333: _foo(T &&, tag_string)

    foo(const char * const &)
    222: _foo(T &&, tag_string)

    foo(const char * const &)
    555: _foo(T &&, tag_string)

    foo(const char (&)[])
    666: _foo(T &&, tag_string)

    foo(std::string &&)
    777: _foo(T &&, tag_string)

    foo(const std::string &)
    888: _foo(T &&, tag_string)

https://godbolt.org/z/hgs7Vh

https://rextester.com/GJZ41642

Andry
  • 2,273
  • 29
  • 28
1

I just wanted to share my solution for this problem:

f() has two kinds of overloads: One for char arrays and one for "everything else".

f2() handles the "everything else" case.

That's the most clean workaround IMHO.

#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[%d]: %s\n", N, s); }

template<size_t N>
void f(char(&s)[N]) // Handle char array
{ std::printf("char[%d]: %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");
}
braph
  • 11
  • 2
-2

The problem is that this function takes its argument by value:

template<typename arr_t>
void foo(arr_t arr) 

You cannot pass arrays by value, they decay to pointers, so when you call it with an array with a known length it instantiates foo<const char*>(const char*) or foo<char*>(char*) (depending on the const-ness of the array) and you lose the length.

You just want:

template<size_t N>
void foo(const char (&)[N])
{
   std::printf("const array\n");
}

template<size_t N>
void foo(char (&)[N])
{
   std::printf("non-const array\n");
}

void foo(const char*)
{
   std::printf("not an array\n");
}
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • 1
    This is not what I want, because of foo1("aa") print "not an array", while I want "const array". There is conversation from "aa" to const char (&)[3], see my "f" example. – user1244932 Sep 11 '15 at 00:22
  • @user1244932: So don't have the `const char*` overload...? I don't get the problem at all. If you want a function that takes reference-to-array, just write it. Why did you introduce other functions? – Lightness Races in Orbit Sep 11 '15 at 00:23
  • 1
    I want distinguish between two variants: (a) compiler has knowledge about length of char array at compile time, (b) compiler no knowledge about length at compile time. In variant (a) I want introduce two overload one for const array, and one for not const. In variant (b) I want just to catch such variants. – user1244932 Sep 11 '15 at 00:29
  • 2
    @user1244932 the solution is pretty much there already. `void foo(arr_t arr) ` fails because you can't pass arrays per value. Change it to reference and your original version works fine. – AliciaBytes Sep 11 '15 at 00:34
  • 3
    This [doesn't actually work](http://melpon.org/wandbox/permlink/IkZ0HSX7HiDEJk3B). If you write `foo("aa")` it prints "not an array" instead of "const array". – Chris Drew Sep 11 '15 at 00:58
  • 1
    @ChrisDrew, drat, of course. I simplified it too much and didn't test the end result! – Jonathan Wakely Sep 11 '15 at 09:17