4

I am trying to have a function take a generic std::vector (template<typename T> std::vector<T>, and then call a template function, which has specialization for a specific (abstract) type, on all its elements.

I'm trying to figure out how to use the specialized version while still being able to use the generic version, but I haven't been successful.

I am using visual studio 2019.

This is my code:

#include <iostream>
#include <vector>

//the abstract type
struct Foo
{
    virtual int bar(unsigned int) const = 0;
};

//The 'template function' I mentioned
template<typename T>
void do_something_with_an_element(const T& value)
{
    std::cout << value;
}

//the specialised version I mentioned
template<>
void do_something_with_an_element<Foo>(const Foo& value)
{
    //Note: this is only a placeholder
    std::cout << "It's a foo. bar = " << value.bar(7) << std::endl;
}

//the function that takes the 'generic' std::vector
template<typename T>
void do_something_with_a_vector(const std::vector<T>& vec)
{
    for (auto element : vec)
    {
        //calling the function on all its elements
        do_something_with_an_element(element); //Here is the problem line
    }
}

struct foo_impl : public Foo
{
    int val;

    foo_impl(int _val)
        : val(_val)
    {}

    // Inherited via Foo
    virtual int bar(unsigned int _something) const override
    {
        //do whatever...
        return val;
    }
};

std::vector<int> int_vector = { 32, 3, 43, 23 }; 
std::vector<foo_impl> foo_vector = { foo_impl(3), foo_impl(9), foo_impl(13) };

int main()
{
    do_something_with_a_vector(int_vector); //fine
    do_something_with_a_vector(foo_vector); //compile error
}


This is my error:

        1>program.cpp
        1>C:\...\program.cpp(17): error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const T' (or there is no acceptable conversion)
        1>        with
        1>        [
        1>            T=foo_impl
        1>        ]
        1>C:\...\MSVC\14.20.27508\include\ostream(438): note: could be 'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(std::basic_streambuf<char,std::char_traits<char>> *)'
        1>C:\...\MSVC\14.20.27508\include\ostream(413): note: or       'std::basic_ostream<char,std::char_traits<char>> &std::basic_ostream<char,std::char_traits<char>>::operator <<(const void *)'
         ((list goes on...))
        1>C:\...\program.cpp(17): note: while trying to match the argument list '(std::ostream, const T)'
        1>        with
        1>        [
        1>            T=foo_impl
        1>        ]
        1>C:\...\program.cpp(35): note: see reference to function template instantiation 'void do_something_with_an_element<foo_impl>(const T &)' being compiled
        1>        with
        1>        [
        1>            T=foo_impl
        1>        ]
        1>C:\...\program.cpp(61): note: see reference to function template instantiation 'void do_something_with_a_vector<foo_impl>(const std::vector<foo_impl,std::allocator<_Ty>> &)' being compiled
        1>        with
        1>        [
        1>            _Ty=foo_impl
        1>        ]
        1>Done building project "program.vcxproj" -- FAILED.
        ========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========```
JeJo
  • 30,635
  • 6
  • 49
  • 88
null
  • 171
  • 1
  • 9
  • 1
    I don't know why exactly the specialization didn't work. but providing a non-template simple function overload will do the job, as the compiler consider it in first priority over the templated one. – JeJo Apr 20 '19 at 08:40
  • 1
    @JeJo That's what I thought at first too, but it doesn't work, try it! It starts working if the argument type is changed to `const foo_impl &` rather than `const Foo &` but that may of course not be desirable. [Overload resolution](https://en.cppreference.com/w/cpp/language/overload_resolution) in C++ is rather complicated... – Thomas Apr 20 '19 at 08:56

1 Answers1

4

In order to choose a specialization of const Foo&, implicit conversion from foo_impl to Foo should take place. On the other hand, by choosing const T& the compiler does a clever move that it directly deduce to the type(i.e foo_impl) as it is easier for the compiler.

Hence the specialization of Foo is rejected and the error came up for no overload found for operator<<(std::cout, foo_impl).


Update : In order to choose the right specialization, by preventing the compiler to instantiate a const T& for foo_impl, you can use the SFINAE along with overload resolution. (creadits @Hiroki)

template<typename T>
std::enable_if_t<!std::is_base_of_v<Foo, T>>
do_something_with_an_element(const T& value)
{
    std::cout << value;
}

void do_something_with_an_element(const Foo& value)
{
    std::cout << "It's a foo. bar = " << value.bar(7) << std::endl;
}
Indiana Kernick
  • 5,041
  • 2
  • 20
  • 50
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • How do I make the implicit conversion happen? should there be a conversion operator `operator const Foo& () const` in `foo_impl`? – null Apr 20 '19 at 09:19
  • 1
    @null [SFINAE and overload](https://stackoverflow.com/questions/13404755/c-template-specialization-for-interface) would well work, https://wandbox.org/permlink/Up0maEHckdhEETe1. – Hiroki Apr 20 '19 at 09:20
  • 1
    @Hiroki Hmm... didn't think of that. Thatks for the link. I was still looking for relevant citation form doc, to the answer. Added to the answer. – JeJo Apr 20 '19 at 09:35
  • 1
    @JeJo thx. Yeah, I also don't know the exactly relevant paragraphs to this post. – Hiroki Apr 20 '19 at 09:47