C++ check whether constructor contains a parameter of given type
Asked Answered
D

1

7

With std::is_constructible one can question some given type for the presence of a certain constructor:

struct A {};
struct B
{
     explicit B(int, A, double) {}
};

int main()
{
    std::cout<<std::is_constructible<B,int,A,double>::value<<std::endl; //prints true
}

Suppose one does not know type B. Is there also a way to check whether there exists a constructor in B which contains type A, regardless of the other parameters? (--or, already sufficient, which contains type A in the n-th position?)


Given a non-explicit constructor, I figured out a workaround by using a type which can be implicitly converted to anything:

struct convert_to_anything
{
    template<typename T>
    operator T() const
    {
        return T{};    
    }
};

int main()
{
    std::cout<<std::is_constructible<B, convert_to_anything, A, convert_to_anything>::value<<std::endl;
}

(Actually, and unexpected to me, I found empirically that it seems to work as well when explicit is added to the constructor of B ... whereas I thought it would prevent from conversions?)

Still, with this workaround I would have to test all possible numbers of parameters. Say for an A in the first position:

std::is_constructible<B, A>::value
|| std::is_constructible<B, A, convert_to_anything>::value
|| std::is_constructible<B, A, convert_to_anything, convert_to_anything>::value
//... and so on up to a chosen maximum size.

That seems a bit unsatisfying. Do you have any better workarounds?

Daley answered 19/4, 2015 at 21:12 Comment(10)
"With std::is_constructible one can question some given type for the presence of a certain constructor:" Actually, A might have some conversion operator yielding int and B takes an int instead. We can't know.Terrellterrena
Or even, A is constructible from int. How do we determine specifically that it's int and not char or long?Piddling
Such testing is done with SFINAE, and it works only when you know what exactly you are looking for. It's impossible to find a constructor with arbitrary properties, because there could me many such constructors, and C++ type system has no representation for "either this or that" types.Shenitashenk
@Columbo: agreed, in the worst case there is nothing to do. But in the simplest case, assume I know these classes as I've coded them, and I never built in any conversion.Daley
@polkovnikov.ph: look at my workaround in the end of the question. It works (--does it really?) though I do not know a certain type to search for beside A.Daley
@Daley convert_to_anything works not as you might expect, and also you're stuck with some upper bound on the number of constructor's arguments.Shenitashenk
@polkovnikov.ph: could you please explain why it doesn't work as expected? At least std::is_constructible<B, convert_to_anything, A, convert_to_anything>::value gives true for the example above, and the upper limit of constructor arguments is not really practically relevant ... I have no constructors taking 57 arguments :-)Daley
you can workaround 'number of arguments' with compile time cycle (recursion) with some template metaprogramming as suggested here #3082613Peppery
@PeterK: thanks for your comment, that was already clear to me although I didn't state it explicitly in the question ... my last codeblock was meant to be evaluated by compile-time recursion. My question was rather, whether this is a correct approach and whether there are better ones.Daley
I see this approach as quite working - indeed if you limit number of ctor args with reasonable bound, say 7 you can indeed generate all the instantiations with metaprogramming (with size_t template arguments). I can also suggest an approach which we like and use - to make a program to write all the code required and use it via build systemPeppery
D
6

No, there is essentially no other way to accomplish this. As you suggest, one can use compile-time metaprogramming to manually unroll the permutations. I believe the generic implementation below is about as good as it can possibly be. See the has_constructor_taking alias template and its usage at the bottom of the code.

The code below uses the template_worm technique I describe here, which is a more fleshed-out implementation of your convert_to_anything. The code works on recent versions of both Clang and GCC.

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

namespace detail {

    //template_worm CANNOT be used in evaluated contexts
    struct template_worm {

        template<typename T>
        operator T& () const;

        template<typename T>
        operator T && () const;

        template_worm() = default;

        template<typename... T>
        template_worm(T&&...);

        template_worm operator+() const;
        template_worm operator-() const;
        template_worm operator*() const;
        template_worm operator&() const;
        template_worm operator!() const;
        template_worm operator~() const;
        template_worm operator()(...) const;
    };

#define TEMPLATE_WORM_BINARY_OPERATOR(...)                                 \
                                                                           \
    template<typename T>                                                   \
    constexpr inline auto                                                  \
    __VA_ARGS__ (template_worm, T&&) -> template_worm {                    \
        return template_worm{};                                            \
    }                                                                      \
                                                                           \
    template<typename T>                                                   \
    constexpr inline auto                                                  \
    __VA_ARGS__ (T&&, template_worm) -> template_worm {                    \
        return template_worm{};                                            \
    }                                                                      \
                                                                           \
    constexpr inline auto                                                  \
    __VA_ARGS__ (template_worm, template_worm) -> template_worm {          \
        return template_worm{};                                            \
    }                                                                      \
    /**/

    TEMPLATE_WORM_BINARY_OPERATOR(operator+)
    TEMPLATE_WORM_BINARY_OPERATOR(operator-)
    TEMPLATE_WORM_BINARY_OPERATOR(operator/)
    TEMPLATE_WORM_BINARY_OPERATOR(operator*)
    TEMPLATE_WORM_BINARY_OPERATOR(operator==)
    TEMPLATE_WORM_BINARY_OPERATOR(operator!=)
    TEMPLATE_WORM_BINARY_OPERATOR(operator&&)
    TEMPLATE_WORM_BINARY_OPERATOR(operator||)
    TEMPLATE_WORM_BINARY_OPERATOR(operator|)
    TEMPLATE_WORM_BINARY_OPERATOR(operator&)
    TEMPLATE_WORM_BINARY_OPERATOR(operator%)
    TEMPLATE_WORM_BINARY_OPERATOR(operator,)
    TEMPLATE_WORM_BINARY_OPERATOR(operator<<)
    TEMPLATE_WORM_BINARY_OPERATOR(operator>>)
    TEMPLATE_WORM_BINARY_OPERATOR(operator<)
    TEMPLATE_WORM_BINARY_OPERATOR(operator>)

    template<typename T>
    struct success : std::true_type {};

    template<typename T, typename... Args>
    struct try_construct {
        static constexpr bool value = std::is_constructible<T, Args...>::value;
    };

    template<typename T>
    struct try_construct<T, void> {

        template<typename U>
        static auto test(int) ->
            success<decltype(U())>;

        template<typename>
        static std::false_type test(...);

        static constexpr const bool value = decltype(test<T>(0))::value;
    };

    template<typename T, typename ArgTuple, typename MappedSeq>
    struct try_construct_helper;

    template<typename T, typename ArgTuple, std::size_t... I>
    struct try_construct_helper<T, ArgTuple, std::index_sequence<I...>> {
        using type = try_construct<T, typename std::tuple_element<I, ArgTuple>::type...>;
    };

    struct sentinel {};

    template<typename Target>
    using arg_map = std::tuple<Target, template_worm const &>;

    constexpr const std::size_t MappedTargetIndex = 0;
    constexpr const std::size_t MappedWormIndex = 1;

    template<std::size_t>
    using worm_index = std::integral_constant<std::size_t, MappedWormIndex>;

    template<typename SeqLeft, typename SeqRight>
    struct map_indices;

    template<std::size_t... Left, std::size_t... Right>
    struct map_indices<std::index_sequence<Left...>, std::index_sequence<Right...>> {

        using type = std::index_sequence<
            worm_index<Left>::value...,
            MappedTargetIndex,
            worm_index<Right>::value...
        >;
    };

    template<std::size_t... Right>
    struct map_indices<sentinel, std::index_sequence<Right...>> {
        using type = std::index_sequence<0, worm_index<Right>::value...>;
    };

    template<std::size_t... Left>
    struct map_indices<std::index_sequence<Left...>, sentinel> {
        using type = std::index_sequence<worm_index<Left>::value..., 0>;
    };

    template<>
    struct map_indices<sentinel, sentinel> {
        using type = std::index_sequence<0>;
    };

    template<std::size_t IncrementBy, typename Seq>
    struct increment_seq;

    template<std::size_t IncrementBy, std::size_t... I>
    struct increment_seq<IncrementBy, std::index_sequence<I...>> {
        using type = std::index_sequence<(I + IncrementBy)...>;
    };

    // Checks the U constructor by passing TargetArg in every argument slot recursively
    template<typename U, typename TargetArg, std::size_t TargetIndex, std::size_t Max, typename SeqOrSentinel>
    struct try_constructors;

    template<typename U, typename TargetArg, std::size_t TargetIndex, std::size_t Max>
    struct try_constructors<U, TargetArg, TargetIndex, Max, sentinel> {
        static constexpr const bool value = false;
    };

    template<typename U, typename TargetArg, std::size_t TargetIndex, std::size_t Max, std::size_t... I>
    struct try_constructors<U, TargetArg, TargetIndex, Max, std::index_sequence<I...>> {

        using next = typename std::conditional<
            sizeof...(I)+1 <= Max,
            std::make_index_sequence<sizeof...(I)+1>,
            sentinel
        >::type;

        using args = arg_map<TargetArg>;

        using left_seq = typename std::conditional<
            TargetIndex == 0,
            sentinel,
            std::make_index_sequence<TargetIndex>
        >::type;

        using right_seq_detail = typename increment_seq<
            TargetIndex,
            std::make_index_sequence<sizeof...(I)-TargetIndex>
        >::type;

        using right_seq = typename std::conditional<
            TargetIndex == (sizeof...(I)),
            sentinel,
            right_seq_detail
        >::type;

        using mapped_seq = typename map_indices<left_seq, right_seq>::type;

        static constexpr const bool value = std::disjunction<
            typename try_construct_helper<U, args, mapped_seq>::type,
            try_constructors<U, TargetArg, TargetIndex, Max, next>
        >::value;
    };

    // unrolls the constructor attempts using the argument counts in the SearchSeq range
    template<typename T, typename TargetArg, typename SearchSeq>
    struct try_constructors_outer;

    template<typename T, typename TargetArg, std::size_t... TargetIndices>
    struct try_constructors_outer<T, TargetArg, std::index_sequence<TargetIndices...>> {

        static constexpr const bool value = std::disjunction<
            try_constructors<
                T,
                TargetArg,
                TargetIndices,
                sizeof...(TargetIndices),
                std::make_index_sequence<TargetIndices>
            >...
        >::value;
    };

    template<typename T, std::size_t... TargetIndices>
    struct try_constructors_outer<T, void, std::index_sequence<TargetIndices...>> {
        static constexpr const bool value = try_construct<T, void>::value;
    };
}

// Here you go.
template<typename TargetArg, typename T, std::size_t SearchLimit = 4>
using has_constructor_taking = std::integral_constant<bool,
    detail::try_constructors_outer<
        T,
        TargetArg,
        std::make_index_sequence<SearchLimit>
    >::value
>;

struct A {};

struct B {
    B(int, A, double) {}
};

struct C {
    C() = delete;
    C(C const &) = delete;
};

static_assert(has_constructor_taking<A, B>::value, "");
static_assert(has_constructor_taking<int, B>::value, "");
static_assert(has_constructor_taking<double, B>::value, "");
static_assert(!has_constructor_taking<C, B>::value, "");
static_assert(!has_constructor_taking<const char*, B>::value, "");

static_assert(has_constructor_taking<void, A>::value, "");
static_assert(has_constructor_taking<A const &, A>::value, "");

static_assert(!has_constructor_taking<void, C>::value, "");
static_assert(!has_constructor_taking<C const &, C>::value, "");

int main() {}
Dividers answered 11/5, 2016 at 17:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.