0

Is there a good way to evaluate an explicit constructor exists for multiple arguments? This is very similar to this question, except that std::is_convertible won't work for this case because we have multiple arguments being passed to the constructor we're testing for.

For example:

#include <iostream>
#include <type_traits>

struct InitParams
{
    int Parameter1;
    int Parameter2;
};

class ExampleFloatConstructible
{
public:
    explicit ExampleFloatConstructible(float InValue, const InitParams& InParams);
};

class ExampleIntConstructible
{
public:
    explicit ExampleIntConstructible(int InValue, const InitParams& InParams);
};

template<typename ClassToTest, typename ArgType>
struct IsExplicitlyConstructibleWithSettings
{
    static constexpr bool value = std::is_constructible<ClassToTest, ArgType, const InitParams&>::value;
};

int main()
{
    // "Correct" values:
    // will be true:
    std::cout << "ExampleFloatConstructible Can Be Built from float? " << 
        IsExplicitlyConstructibleWithSettings<ExampleFloatConstructible, float>::value << std::endl;
    
    // will be true:
    std::cout << "ExampleIntConstructible Can Be Built from int? " << 
        IsExplicitlyConstructibleWithSettings<ExampleIntConstructible, int>::value << std::endl; 

    // "Incorrect" values:
    // will also be true, because int is convertible from float
    std::cout << "ExampleIntConstructible Can Be Built from float? " <<  
        IsExplicitlyConstructibleWithSettings<ExampleIntConstructible, float>::value << std::endl;

    // will also be true, because float is convertible from int
     std::cout << "ExampleFloatConstructible Can Be Built from int? " << 
        IsExplicitlyConstructibleWithSettings<ExampleFloatConstructible, int>::value << std::endl; 
}

Here's the above example in compiler explorer.

Kadinski
  • 161
  • 14
  • How `is_convertible`would work with multiple parameters ? – Cleiton Santoia Silva Jul 17 '20 at 22:57
  • 2
    @CleitonSantoiaSilva, That's the point of the question. – chris Jul 17 '20 at 22:57
  • Well, if your call has more than 1 parameter, then you not even need to ask about "is_convertible" right ? – Cleiton Santoia Silva Jul 17 '20 at 23:00
  • 1
    @CleitonSantoiaSilva, If you don't do that half, you get implicit+explicit conversions considered. The poster wants to disallow implicit conversions in the test. – chris Jul 17 '20 at 23:04
  • 1
    @CleitonSantoiaSilva is_convertible was just brought up in reference to this other question, which is able to use is_convertible to test if an explicit constructor exists for a constructor with one argument: https://stackoverflow.com/questions/42786565/how-to-check-if-type-is-explicitly-implicitly-constructible – Kadinski Jul 17 '20 at 23:06
  • I mean that you only need to check `is_convertible` in one-parameter constructors – Cleiton Santoia Silva Jul 18 '20 at 08:46

1 Answers1

0

Unfortunately, I believe you'd have to make your own version where you pass {args} to the pretend function instead of arg (live example):

// Can we call this function with {args}?
template<typename To>
void conversion_test(To);

// If passing {From...} as an arg to conversion_test<To> fails, return false.
template<typename To, typename... From>
constexpr bool sfinae_helper(...) {
    return false;
}

// If passing {From...} as an arg to conversion_test<To> succeeds, beat the ellipsis and return true.
template<typename To, typename... From>
constexpr auto sfinae_helper(int) 
  -> decltype(conversion_test<To>({std::declval<From>()...}), true) {
    return true;
}

// Fall back to the standard trait when possible. You might want to make this name a struct for consistency.
template<typename To, typename... From>
constexpr bool is_multi_convertible_to() {
    if constexpr (sizeof...(From) == 1) {
        return std::is_convertible_v<From..., To>;
    } else {
        return sfinae_helper<To, From...>(0);
    }
}

template<typename To, typename... From>
constexpr bool is_multi_convertible_to_v = is_multi_convertible_to<To, From...>();

struct has_implicit { 
    has_implicit(int, int) {}
};

struct has_explicit {
    explicit has_explicit(int, int) {}
};

// Warning: Not a comprehensive test suite!
static_assert(is_multi_convertible_to_v<int, double>);
static_assert(not is_multi_convertible_to_v<char*, const char*>);
static_assert(is_multi_convertible_to_v<has_implicit, int, int>);
static_assert(not is_multi_convertible_to_v<has_explicit, int, int>);

Notably, Concepts would remove the sfinae_helper layer, but this should work in C++17. Be sure not to assume it works perfectly as is.

chris
  • 60,560
  • 13
  • 143
  • 205
  • This is way too complex, try to write a function in your class and check it... – Yunfei Chen Jul 18 '20 at 03:09
  • @YunfeiChen, I'd be happy to see a simpler solution that compiles and does what the poster wants. I wouldn't doubt there's something simpler out there. – chris Jul 18 '20 at 03:10
  • Actually, I'm fairly certain this is what's necessary here. Testing this out on my problem it seems to do the trick (at least for two argument constructors). Thanks, Chris! – Kadinski Jul 18 '20 at 06:49