How to call a templated function if it exists, and something else otherwise?

后端 未结 7 460
忘掉有多难
忘掉有多难 2021-01-30 10:58

I want to do something like

template 
void foo(const T& t) {
   IF bar(t) would compile
      bar(t);
   ELSE
      baz(t);
}
相关标签:
7条回答
  • 2021-01-30 11:22

    If you're willing to limit yourself to Visual C++, you can use the __if_exists and __if_not_exists statements.

    Handy in a pinch, but platform specific.

    0 讨论(0)
  • 2021-01-30 11:32
    //default
    
    ////////////////////////////////////////// 
        template <class T>
        void foo(const T& t){
            baz(t);
        }
    
    //specializations
    //////////////////////////////////////////  
    
        template <>
        void foo(const specialization_1& t){
            bar(t);
        }
        ....
        template <>
        void foo(const specialization_n& t){
            bar(t);
        }
    
    0 讨论(0)
  • 2021-01-30 11:36

    litb has given you a very good answer. However, I wonder whether, given more context, we couldn't come up with something that's less generic, but also less, um, elaborate?

    For example, what types can be T? Anything? A few types? A very restricted set which you have control over? Some classes you design in conjunction with the function foo? Given the latter, you could simple put something like

    typedef boolean<true> has_bar_func;
    

    into the types and then switch to different foo overloads based on that:

    template <typename T>
    void foo_impl(const T& t, boolean<true> /*has_bar_func*/);
    template <typename T>
    void foo_impl(const T& t, boolean<false> /*has_bar_func*/);
    
    template <typename T>
    void foo(const T& t) {
      foo_impl( t, typename T::has_bar_func() );
    }
    

    Also, can the bar/baz function have just about any signature, is there a somewhat restricted set, or is there just one valid signature? If the latter, litb's (excellent) fallback idea, in conjunction with a meta-function employing sizeof might be a bit simpler. But this I haven't explored, so it's just a thought.

    0 讨论(0)
  • 2021-01-30 11:37

    EDIT: I spoke too soon! litb's answer shows how this can actually be done (at the possible cost of your sanity... :-P)

    Unfortunately I think the general case of checking "would this compile" is out of reach of function template argument deduction + SFINAE, which is the usual trick for this stuff. I think the best you can do is to create a "backup" function template:

    template <typename T>
    void bar(T t) {   // "Backup" bar() template
        baz(t);
    }
    

    And then change foo() to simply:

    template <typename T>
    void foo(const T& t) {
        bar(t);
    }
    

    This will work for most cases. Because the bar() template's parameter type is T, it will be deemed "less specialised" when compared with any other function or function template named bar() and will therefore cede priority to that pre-existing function or function template during overload resolution. Except that:

    • If the pre-existing bar() is itself a function template taking a template parameter of type T, an ambiguity will arise because neither template is more specialised than the other, and the compiler will complain.
    • Implicit conversions also won't work, and will lead to hard-to-diagnose problems: Suppose there is a pre-existing bar(long) but foo(123) is called. In this case, the compiler will quietly choose to instantiate the "backup" bar() template with T = int instead of performing the int->long promotion, even though the latter would have compiled and worked fine!

    In short: there's no easy, complete solution, and I'm pretty sure there's not even a tricky-as-hell, complete solution. :(

    0 讨论(0)
  • 2021-01-30 11:38

    I think litb's solution works, but is overly complex. The reason is that he's introducing a function fallback::bar(...) which acts as a "function of last resort", and then goes to great lengths NOT to call it. Why? It seems we have a perfect behavior for it:

    namespace fallback {
        template<typename T>
        inline void bar(T const& t, ...)
        {
            baz(t);
        }
    }
    template<typename T>
    void foo(T const& t)
    {
        using namespace fallback;
        bar(t);
    }
    

    But as I indicated in a comment to litb's original post, there are many reasons why bar(t) could fail to compile, and I'm not certain this solution handles the same cases. It certainly will fail on a private bar::bar(T t)

    0 讨论(0)
  • 2021-01-30 11:47

    Are you not able to use full specialisation here (or overloading) on foo. By say having the function template call bar but for certain types fully specialise it to call baz?

    0 讨论(0)
提交回复
热议问题