2

I am hoping to achieve such type matching function:

    MyClass<int, bool> temp;
    temp.print(-1, false);           // works
    temp.print(-1);                  // compile error, too less argument
    temp.print(-1, "string");        // compile error, wrong type
    temp.print(-1, false, 'c');      // compile error, too many arguments

Basically, when given template parameter, MyClass's function print accepts arguments of exactly the same type, no more, no less.

And this is my current implementation:

template<class... ClassType>
class MyClass {
public:
    template<typename... ArgType>
    void print(ArgType... args) {
        matchType<ClassType...>(args...);
    }

private:
    template<typename Type>
    void matchType(Type t) {
        std::cout << "matching " << t << "\n";
        std::cout << "end\n";
    }

    template<typename FirstType, typename... Rest>
    void matchType(FirstType firstArg, Rest... args) {
        std::cout << "matching " << firstArg << "\n";
        matchType<Rest...>(args...);
    }
};

But it fails to detect and match type that it compiles well for the code:

    MyClass<int, bool> temp;
    temp.print(-1, false);           // works
    temp.print(-1, "string");        // works, shouldn't work
    temp.print(-1, false, 'c');      // works, shouldn't work

Can someone explain to me what have I done wrong?

live code

Koothrappali
  • 157
  • 6
  • What is the underlying problem you're trying to solve? Although the question as asked seems answerable, it doesn't seem to have value beyond learning exercise. – Incomputable Jul 06 '21 at 11:25
  • `"string"` is a `const char*`, which in turn can be converted to both `int` and `bool`. – super Jul 06 '21 at 11:28
  • @Incomputable The real big problem has been simplified. This code snippet describe my current trouble. – Koothrappali Jul 06 '21 at 11:32
  • @super That's true but it doesn't break when too many arguments fed in. – Koothrappali Jul 06 '21 at 11:33
  • Why would it break? You have a variadic template function that accepts any number of arguments. The fact that you are explicitly specifying only 2 of them doesn't stop the function from deducing the rest. – super Jul 06 '21 at 11:35
  • @Koothrappali, you can do SFINAE on the templates of a function, e.g. `template , std::tuple>`. I still do not think this is a good idea. – Incomputable Jul 06 '21 at 11:35
  • As a counter example, doing `temp.matchType(1,2, "hey", 3.5);` is also perfectly valid. First type is specified. Rest is deduced. – super Jul 06 '21 at 11:37
  • @super I'm hoping `temp.print(1,2, "hey", 3.5);` to break as well, but currently have no idea how to do that. – Koothrappali Jul 06 '21 at 11:38
  • @Incomputable SFINAE is a good idea, could you elaborate it? – Koothrappali Jul 06 '21 at 11:40

2 Answers2

3
#include <iostream>

template<class... ClassType>
class MyClass {
public:
    template<typename... ArgType>
    void print(ArgType... args) = delete;

    void print(ClassType ... args) {
        std::cout << "works\n";
    }
};

int main()
{
    MyClass<int, bool> temp;
    temp.print(-1, false);            // ok

    temp.print(-1);                   // error
    temp.print(-1, "string");         // error
    temp.print(-1, false, 'c');       // error
    temp.print(-1L, false);           // error
}
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
1

To make it work, you have to check at compile time that ArgType... and ClassType... are the same. This does the job:

#include <type_traits>
#include <utility>
#include <tuple>

template<class... ClassType>
class MyClass {
private:
    template <typename... ArgType, std::size_t... I>
    static constexpr bool check_type_impl(std::index_sequence<I...>) {
        return (std::is_same_v<typename std::tuple_element<I, std::tuple<ClassType...>>::type,
                               typename std::tuple_element<I, std::tuple<ArgType...>>::type> && ...); }

    template <typename... ArgType>
    static constexpr bool check_type() {
        return (sizeof...(ArgType) == sizeof...(ClassType)) &&
                check_type_impl<ArgType...>(std::index_sequence_for<ClassType...>{});
        }

public:
    template<typename... ArgType, class = std::enable_if_t<check_type<ArgType...>()>>
    void print(ArgType... args) {
    }

};

int main()
{
  MyClass<int, bool> temp;
  temp.print(-1, false);           // works
  //temp.print(-1);                  // compile error, too less argument
  //temp.print(-1, "string");        // compile error, wrong type
  //temp.print(-1, false, 'c');      // compile error, too many arguments
};

See it Live on Coliru.

The member function check compares one-by-one the type of ArgType... and ClassType.... It does that by passing to check_impl an std::index_sequence of indices from 0 to sizeof...(ClassType). check_impl returns true if the I-th types are identical. See this question for the use of std::tuple_element to extract the type of I-th element of a variadic pack.

francesco
  • 7,189
  • 7
  • 22
  • 49