Dr.Knowitall
Dr.Knowitall

Reputation: 10468

How can I build a function concept with a particular signature?

I'm writing some generic software using concepts and I want to check if a particular function symbol name exists with a signature of (void)(int,int) on a struct. To do this I've I'm thinking of approaching this problem through template specialization but I'm sort of lost.

What I want is something to work and through compile time errors if the concept is not satisfied like so:

struct TypeA {
  // Passes concept
  void process (int a ,int b) const {};
};

struct TypeB {
  // Does not pass concept
  void process (float a) const {};
};

struct TestConcepts {
  /* concept code here */
  TestConcepts(T t) {
    process_concept(t.process);
  };
};

int main(void) {
  // Should pass
  TestConcept(TypeA{});
  // Should throw error
  TestConcept(TypeB{});
  return 0;
}

I'm having a hard time filling in the blanks but this is what I have so far:

struct TestConcepts {
      /* concept code here */
      struct process_concept {
        process_concept((V*)(IA,IB)){
            if (is_integral<IA>::value && is_integral<IB>::value && is_same<V, void>) {
                return;
            }
            static_assert(false, "You must provide function called process of type (void)(int,int)");
        };
    };
      TestConcepts(T t) {
        process_concept(&t.process);
      };
    };

Unfortunately this doesn't work. How can I get this function signature correct?

Upvotes: 2

Views: 96

Answers (1)

JHBonarius
JHBonarius

Reputation: 11261

How about using a function that returns a declared function pointer?

struct TypeA {
    // Passes concept
    void process (int a ,int b) const {};
};

struct TypeB {
    // Does not pass concept
    void process (float a) const {};
};

template<typename T>
auto TestConcepts(T) -> void(T::*)(int, int) const
{
    return &T::process;
}

int main(void) {
    // Should pass
    TestConcepts(TypeA{});
    // Should throw error
    TestConcepts(TypeB{});
    return 0;
}

Output:

Error(s):

source_file.cpp: In instantiation of ‘void (T::* TestConcepts(T))(int, int) const [with T = TypeB]’:
source_file.cpp:26:23:   required from here
source_file.cpp:19:16: error: cannot convert ‘void (TypeB::*)(float) const’ to ‘void (TypeB::*)(int, int) const’ in return
     return &T::process;
                ^

EDIT: more options

If you want to include void process(long int a, long int b) const; or void process(int a, int b, int c=0) const;, like aschepler is suggesting, you can use type traits.

struct TypeA {
    // Passes concept
    void process(int a, int b) const {};
};

struct TypeB {
    // Does not pass concept
    void process(float a) const {};
};

struct TypeC {
    // Passes concept
    void process(long int a, long int b) const {};
};

struct TypeD {
    // Passes concept
    void process(int a, int b, int c = 0) const {};
};

struct TypeE {
    // Does not pass concept
    void process(int a, int b, int c) const {};
};

#include <type_traits>
template<typename T, typename A1, typename A2, typename... An>
typename std::enable_if<
    std::is_integral<A1>::value &&
    std::is_integral<A2>::value
>::type
TestProcess(const T& t, void(T::*)(A1, A2, An...) const) {
    t.process(1, 2);
};

template<typename T>
void TestConcepts(const T& t)
{
    TestProcess(t, &T::process);
}

int main(void) {
    // Passes
    TestConcepts(TypeA{});
    // Throws compilation error
    TestConcepts(TypeB{});
    // Passes
    TestConcepts(TypeC{});
    // Passes
    TestConcepts(TypeD{});
    // Throws compilation error
    TestConcepts(TypeE{});

    return 0;
}

Upvotes: 2

Related Questions