c++template-specializationc++23

template class method specialization compilation error


I'm making my own test framework in c++ in order to learn more of this language (and wow how much I learned), but I've just hit a critical point -> I try to specialize my contains check to fail directly when it's not a container and else do the real check.

For now my code looks like this:

#include <iostream>
#include <vector>

template<typename ActualType> class AssertionMatcher final {
  public:
    explicit AssertionMatcher(const ActualType &actual): _actual(std::move(actual)) {}

    template<typename ContainedType> auto contains(const ContainedType &contained) const -> void {
        std::cout << "fail";
    }

  private:
    const ActualType _actual;
};

template<typename ContainedType>
class AssertionMatcher<std::vector<ContainedType>> {
  public:
    auto contains(const ContainedType &contained) const -> void {
        std::cout << "ok";
    }
};

template<typename ActualType> [[nodiscard]] auto assertThat(const ActualType &actual) -> AssertionMatcher<ActualType> {
    return AssertionMatcher(actual);
}

And I call it this way:

int main() {
    const std::vector container({1, 2, 3});
    assertThat(container).contains(2);
}

You can find the live example here: https://godbolt.org/z/fxG7a7Tvz

I've looked at many others questions here to find how to achieve that + this page (https://en.cppreference.com/w/cpp/language/template_specialization.html). But this code won't compile (gcc 14 from online compiler):

<source>: In instantiation of 'AssertionMatcher<ActualType> assertThat(const ActualType&) [with ActualType = std::vector<int, std::allocator<int> >]':
<source>:30:22:   required from here
   30 |     (void) assertThat(container);
      |            ~~~~~~~~~~^~~~~~~~~~~
<source>:25:12: error: too many initializers for 'AssertionMatcher<std::vector<int, std::allocator<int> > >'
   25 |     return AssertionMatcher(actual);
      |            ^~~~~~~~~~~~~~~~~~~~~~~~

I have run out of ideas how to fix that. Do you have any ideas?


Solution

  • in (void) assertThat(container); container is a std::vector<int> so in assertThat the form return AssertionMatcher(actual) uses the class template specialization template<typename ContainedType> class AssertionMatcher<std::vector<ContainedType>>, because you don't define any constructor for it there is only the default constructor whose does not have argument, but you give the argument actual.

    Adding in the class template specialization for instance :

    explicit AssertionMatcher(const std::vector<ContainedType> &actual) {
      // does something with actual
    }
    

    and the program compile.

    In main doing (void) assertThat(container).contains(2); rather than (void) assertThat(container); writes ok


    Of course currently assertThat(container).contains(4) also writes ok, you probably want something like

    template<typename ContainedType>
    class AssertionMatcher<std::vector<ContainedType>> {
      public:
        explicit AssertionMatcher(const std::vector<ContainedType> &actual)
          : _actual(std::move(actual)) {}
        auto contains(const ContainedType &contained) const -> void {
            std::cout << ((std::find(_actual.begin(), _actual.end(), contained)
                           == _actual.end())
                          ? "nok" : "ok")
                      << std::endl;
        }
    
      private:
        const std::vector<ContainedType> _actual;
    };
    

    and now

    int main() {
      const std::vector container({1, 2, 3});
       
      assertThat(container).contains(2);
      assertThat(container).contains(4);
      assertThat(123).contains(2);
      return 0;
    }
    

    writes

    ok
    nok
    fail