25

There are two "C" functions:

void fooA(const char*);
void fooW(const wchar_t*);

Then there is a wrapper template function:

template<typename _TChar>
void foo(const _TChar* str)
{
     // call fooA or fooB based on actual type of _TChar
     // std::conditional .. ?
         // fooA(str); 
         // fooW(str);
}

If the caller calls foo("Abc"), this template function should make a compile-time call to fooA. Similiarly, foo(L"Abc") should make the final call to fooW.

How do I do that? I thought of using std::conditional but couldn't make it.

I cannot make fooA or fooB overloaded, since these are C functions.

Ajay
  • 18,086
  • 12
  • 59
  • 105

7 Answers7

17

You can put all your wchar_t versions in a class template, say overloads and their char counter-parts in its specialization as shown below:

template<typename WideCharVersion> 
struct overloads
{
    void foo(wchar_t const * arg)
    {
       FooW(arg);
    }
    //more wchar_t functions
};

template<> 
struct overloads<std::false_type>
{
    void foo(char const * arg)
    {
       FooA(arg);
    }
    //more char functions
};

//a friendly alias!
template<typename T>
using is_wide_char = typename std::is_same<whar_t, T>::type;

And then you can use them as:

template<typename _TChar>
void foo(const _TChar* str)
{
    overloads<is_wide_char<_TChar>>::foo(str);
}

Expression SFINAE makes it easy!

Other way is to use Expression SFINAE which does not require to you write anything like overloads and does the same job with less code:

template<typename _TChar>
void foo(const _TChar* str)
{
    invokeOne(fooA, fooW, str);
}

And then you can implement invokeOne as:

 template<typename F1, typename F2, typename ... Args>
 auto invokeOne(F1 f1, F2 f2, Args && ... args) -> decltype(f1(args...))
 {
     return f1(args...);
 }

 template<typename F1, typename F2, typename ... Args>
 auto invokeOne(F1 f1, F2 f2, Args && ... args) -> decltype(f2(args...))
 {
     return f2(args...);
 }

Have a look at the online demo.

In this approach, you don't have to add the overloads to the overloads class template and to its specialization. Instead you just pass them as arguments to invokeOne which calls the right overload for you.

Hope that helps.

Ajay
  • 18,086
  • 12
  • 59
  • 105
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • In what aspect is this better than the solution of lego and IanM_Matrix1? – tamas.kenez Nov 22 '16 at 09:11
  • @tamas.kenez: The first approach is similar. However, the SFINAE approach is different from their approach, which requires lesser code to get things done. – Nawaz Nov 22 '16 at 09:32
9

Then overload another function. I'm assuming foo does more work and needs to be a template. Then call foo_forward_call, defined as such:

void foo_forward_call(char const* ptr) {
    FooA(ptr);
}

void foo_forward_call(wchar_t const* ptr) {
    FooW(ptr);
}

and at the call site:

template<typename _TChar>
void foo(const _TChar* str)
{
    foo_forward_call(str);
}

in C++1z you'll be able to use constexpr if, but to be honest, I think that overloaded solution is still more readable.

template<typename _TChar>
void foo(const _TChar* str)
{
    if constexpr(std::is_same<_TChar, char>::value) {
        FooA(str);
    } else {
        FooW(str);
    }
}

Alternatively, you can use Boost.Hana's overload:

template<typename _TChar>
void foo(const _TChar* str)
{
    hana::overload(fooA, fooW)(str);
}

demo


By the way: you should eschew using underscore-capital-letter names in your programs. They're reserved for implementation for any use (i.e. macros) and might lead to nasty name clashes.

krzaq
  • 16,240
  • 4
  • 46
  • 61
6

This seems like a really strange thing to do with templates. I would suggest using normal overloading instead:

void foo(const char* p) { fooA(p); }
void foo(const wchar_t* p) { fooW(p); }

If you insist on using template then you can do it like this:

template <typename T>
void foo(const T* p)
{
    // Declare functions here so that calling fooW with const char*
    // and 'calling' fooA with const wchar_t* would not cause compile error.
    void fooA(const T*);
    void fooW(const T*);

    if (std::is_same<char, T>::value)
        fooA(p);
    else
        fooW(p);
}
sdkljhdf hda
  • 1,387
  • 9
  • 17
  • 1
    The linker will try to resolve *both* branches `if-else` and one of them will cause linker-error. If `T` is `char`, `fooW` will cause linker-error, else `fooA`. – Nawaz Nov 22 '16 at 09:29
  • True, but not always. This works with gcc and clang, not with vs. To fix this for vs, empty overloads should be defined, but then why bother with all this template stuff, just use plain overload. – sdkljhdf hda Nov 22 '16 at 13:54
5

I like solving problems in general. So let us design a mechanism to overload stuff.

overload_t<...> takes a set of callables in ... and generates an object which uses standard overload resolution to choose between them, via inheritance of operator():

template<class...Fs>
struct overload_t;
// the case where we have a function object:
template<class F>
struct overload_t<F>:F{
  overload_t(F f):F(std::move(f)){}
  using F::operator();
  // boilerplate to ensure these are enabled if possible:
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;
};
// we cannot inherit from a function pointer.  So
// store one, and write an `operator()` that forwards to it:
template<class R, class...Args>
struct overload_t<R(*)(Args...)>{
  using F=R(*)(Args...);
  F f;
  overload_t(F fin):f(fin){}
  R operator()(Args...args)const{
    return f(std::forward<Args>(args)...);
  }
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;
};
// the case where we have more than type to overload.
// recursively inherit from the one-arg and the rest-of-arg
// and using operator() to bring both of their () into equal standing:
template<class F0, class...Fs>
struct overload_t<F0,Fs...>:
  overload_t<F0>,
  overload_t<Fs...>
{
  using overload_t<F0>::operator();
  using overload_t<Fs...>::operator();
  overload_t(F0 f0, Fs...fs):
    overload_t<F0>(std::move(f0)),
    overload_t<Fs...>(std::move(fs)...)
  {}
  overload_t(overload_t&&)=default;
  overload_t(overload_t const&)=default;
  overload_t& operator=(overload_t&&)=default;
  overload_t& operator=(overload_t const&)=default;
};
// a helper function to create an overload set without
// having to specify types.  Will be obsolete in C++17:
template<class...Fs>
overload_t<Fs...> overload(Fs...fs){ return {std::move(fs)...};}

Now to generate a single object that is an overload of multiple, do this:

overload(FooA,FooW)( str );

And str will be dispatched to either based on the usual overload resolution rules. This is useful elsewhere which is why it is worth writing, and the code at point of use is self documenting.

Live example (wow, wrote it correct the first time!)

There are a number of improvements one can add to the above overload_t.

  • Perfect forwarding of the fs during construction and in the helper function.

  • Balanced Binary tree inheritance instead of linear (important of more than a few overloads are done). This can have runtime and compiletime performance impliciations, especially for a large number of functions.

  • Introspect incoming Fs; if they are overload_t balance the combined tree.

  • In C++17, a func<auto> template that takes a function pointer and returns a function object that statelessly invokes it. Compilers are relatively good at eliding function pointers, but they are better at it when there is no possible runtime state that could alter them.

  • Deciding what to do for overload_t<>. Currently it fails to compile; maybe it should just be an empty struct {}, or even a struct with an uncallable operator().

  • Examine existing libraries, like boost::hana::overload and see what differences there are.

  • Expose the ability to extract which of the overloads would be called, maybe via a static tag_t<F> which_overload_helper( Args... ) const method and template<class...Args> using which_overload = typename decltype( which_overload_helper( std::declval<Args>()... ) )::type;

  • Properly picking overloads when some of the incoming Fss do/do not have a const operator(). Should the function pointer have const, volatile, both or neither on operator()? All 4? How about && vs & overloads?

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
4

Maybe if there were more targetted functions or overloading wasn't the only problem you were trying to solve then it might be worthwhile using templates to get this done.

In this case though, simply write your overloads:

void foo(const char* x) { fooA(x); }
void foo(const wchar_t* x) { fooW(x); }
IanM_Matrix1
  • 1,564
  • 10
  • 8
  • This is what I've done finally. But any change in one function requires change in another one also. – Ajay Nov 11 '16 at 11:17
3

You have a few options.

  1. Use an helper explicitly specialized struct:

    template <typename>
    struct helper;
    
    template<>
    struct helper<char>
    {
        void operator()(const char* x){ FooA(x); }
    };
    
    template<>
    struct helper<wchar_t>
    {
        void operator()(const wchar_t* x){ FooW(x); }
    };
    
    template <typename _TChar>
    void foo(const _TChar* str)
    {
        helper<_TChar>{}(str);
    }
    
  2. Use a "static if" implementation (e.g. boost::hana::eval_if or my own):

    template <typename _TChar>
    void foo(const _TChar* str)
    {
        vrm::core::static_if(std::is_same<_TChar, char>{})
            .then([](const auto* x_str){ FooA(x_str); })
            .else_([](const auto* x_str){ FooW(x_str); })(str);
    }
    
  3. Use an helper overloaded function:

    void helper(const char* x) { FooA(x); }
    void helper(const wchar_t* x) { FooW(x); }
    
    template <typename _TChar>
    void foo(const _TChar* str)
    {
        helper(str);
    }
    
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • I think `.then(FooA).else_(FooW)` should work as well? If not, the designer of the library should make it work as well! – Nawaz Nov 11 '16 at 12:01
  • @Nawaz: it's more complicated - every branch requires needs to be a template that will only be instantiated if the condition matches. If it's not a template, compilation will fail. [I discussed this in my CppCon 2016 talk](https://www.youtube.com/watch?v=aXSsUqVSe2k). – Vittorio Romeo Nov 11 '16 at 12:27
  • I'm sure it is complicated. Still I feel that it can be made to work, by adding one more level of indirection. – Nawaz Nov 11 '16 at 12:38
-1

I think using typeid() is very elegant and readable, but not sure if there is any side effect.

#include <typeinfo>

template <typename T>
void foo(){
    if(typeid(T) == typeid(const char*)){
        fooA();
    }
    else if (typeid(T) == typeid(const wchar_t*)){
        fooB();
    }
}

Check it out in wandbox.

petingo
  • 1
  • 3
  • 1
    Thanks for adding the answer. But this doesn't really answer the question. It uses `typeid` which may require RTTI. Even if the type is determined at compile time, the `if-else` is not compile time expression. templates are for compile time code, typeid goes towards runtime-type information and evaluation. See `if-constexpr` also. – Ajay Aug 17 '22 at 19:47