Implement compile-time function type verification
Company: OpenAI
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Implement a C++20 compile-time utility to verify whether a callable matches a target function type. Requirements: create a primary template is_callable_with_signature<Fn, R(Args...)> that yields true if Fn is invocable with Args... and the return type is convertible to R; provide two implementations—
(
1) SFINAE/traits-based and
(
2) concepts/requires-based; support free functions, lambdas, functors with overloaded operator(), member function pointers, and std::function; include minimal tests with static_assert and examples that should fail to compile if the signature does not match; discuss handling noexcept, cv/ref qualifiers, and implicit conversions.
Quick Answer: This question evaluates proficiency in C++ template metaprogramming, type traits, SFINAE and Concepts for compile-time callable signature verification, including handling of noexcept, cv/ref qualifiers and implicit conversions.
Part 1: Traits-Style Callable Signature Match
You are given the list of callable signatures exposed by a callable object. Think of this as a simplified runtime version of C++ traits such as is_invocable_r.
Each string in 'signatures' represents one overload of the callable. The origin of the overload does not matter: a free function, lambda, functor, member-function pointer, or std::function can all be represented the same way.
A target signature has the form '(A1,A2,...)->R'. A candidate overload matches the target if:
1. It has the same number of parameters.
2. Every target argument type can be implicitly converted to the candidate parameter type.
3. The candidate return type can be implicitly converted to the target return type.
Supported types are: 'bool', 'int', 'long', 'double', 'string', and 'void'.
Allowed implicit conversions are:
- exact type to itself
- bool -> int, long, double
- int -> long, double
- long -> double
- void is only convertible to void
- no other conversions are allowed
Return True if at least one candidate overload matches the target signature; otherwise return False.
Constraints
- 0 <= len(signatures) <= 10000
- Each signature string has length between 4 and 100
- All signatures are well-formed
- Types only come from: bool, int, long, double, string, void
- Only the listed implicit conversions are allowed
Examples
Input: (['(int)->long'], '(int)->double')
Expected Output: True
Explanation: The argument type matches exactly, and the candidate return type long can be widened to double.
Input: (['(double)->int', '(string)->int'], '(int)->long')
Expected Output: True
Explanation: The first overload matches because int can convert to double and int can convert to long on return.
Hints
- Parse each signature into an argument list and a return type. Overloads are just multiple candidate strings.
- Model implicit conversion as a directed rule set or numeric rank order, then test every parameter position plus the return type.
Part 2: Concepts-Style Callable Signature Match with Qualifiers
Now extend the matcher to simulate a stricter concepts/requires-style check.
Each candidate overload and the target signature begin with a bracketed qualifier list, followed by the function type. The format is:
'[q1,q2,...](A1,A2,...)->R'
Examples:
- '[](int)->double'
- '[const,&,noexcept](int)->long'
- '[noexcept]()->void'
Supported qualifiers are:
- 'const'
- '&'
- '&&'
- 'noexcept'
A candidate overload matches the target if all of the following hold:
1. It has the same number of parameters.
2. Every target argument type can be implicitly converted to the candidate parameter type.
3. The candidate return type can be implicitly converted to the target return type.
4. If the target requires 'const', the candidate must include 'const'.
5. If the target requires 'noexcept', the candidate must include 'noexcept'.
6. If the target requires '&', the candidate must include '&'.
7. If the target requires '&&', the candidate must include '&&'.
8. If the target omits a qualifier, the candidate may have it or not.
Use the same type system and implicit conversion rules as in Part 1.
Return True if at least one overload matches; otherwise return False.
Constraints
- 0 <= len(signatures) <= 10000
- Each signature string has length between 6 and 120
- All signatures are well-formed
- Types only come from: bool, int, long, double, string, void
- Qualifiers only come from: const, &, &&, noexcept
- A signature contains at most one of '&' and '&&'
- Only the listed implicit conversions are allowed
Examples
Input: (['[const,&](int)->long', '[&&](int)->double'], '[const,&](int)->double')
Expected Output: True
Explanation: The first overload satisfies both required qualifiers, accepts int, and returns long which can convert to double.
Input: (['[&](int)->double'], '[&,noexcept](int)->double')
Expected Output: False
Explanation: The candidate is missing the required noexcept qualifier.
Hints
- Treat the bracketed qualifier list as a set of properties. The target's qualifiers are requirements; the candidate may have extra ones.
- After qualifier matching, reuse the same argument-conversion and return-conversion logic from Part 1.