Search code examples
c++constructorc++17variadic-templatestype-traits

Variadic constructor with enable_if and is_constructible


I have one class called NameAndTag. It has constructors:

    NameAndTag();
    NameAndTag(std::string name_or_tag);
    NameAndTag(boost::uuids::uuid tag) : Tag(tag) {}

Now, I want to define another class whose (template) constructor takes a variable number of "things" that can be passed to NameAndTag::NameAndTag. Also, I'd like the template types to be implicit.

I have tried different approaches. One that I was hopping to work was this one:

    template<typename... NameOrTag>
    PathToObject(std::enable_if_t<
        std::is_constructible_v<NameAndTag, NameOrTag>
    , NameOrTag&&>... obj_path);

However, I think the compiler has trouble figuring out what are the types for NameOrTag. The compiler says I am passing too many arguments, when the template accepts none. Here is a demo: https://wandbox.org/permlink/10Lwxpt6niHUlaxv

To make the type of NameOrTag easier to figure out, I have tried:

    template<typename... NameOrTag>
    PathToObject(NameOrTag&&... obj_path,
        std::enable_if_t<
            std::conjunction_v
            <
                std::is_constructible_v<NameAndTag, NameOrTag>...
            >
            , bool> dummy = true);

But I suppose that mixing up pack templates and an optional argument is not supposed to work. The compiler says there is no match for PathToObject("name", uuid). Here is a demo: https://wandbox.org/permlink/sVviVKQ6uixYvKJ7

Does what I am trying to do make any sense? How can I accomplish that? I am bound to C++17...


Problem solved! Thank you all! A few comments about my experience, ahead...

Solution given by the accepted answer: https://wandbox.org/permlink/Me3XHlrtZ58ye7Hl

Here is my code: https://github.com/andre-caldas/FreeCAD/blob/0af3ad0d8a9681a1b3931166791b8a93094f4746/src/Base/Accessor/ReferenceToObject.h#L46

Just FYI, since I like trouble, I have divided the template file into ".h" and ".inl" (for inline). Then, because of that, I was getting some sort of "redefinition of default template argument"... because you cannot use the = nullptr when you write the definition of the template. See: https://github.com/andre-caldas/FreeCAD/blob/0af3ad0d8a9681a1b3931166791b8a93094f4746/src/Base/Accessor/ReferenceToObject.inl#L39


Edit: I am bound to C++17.

Edit 2: Added minimum (non-)working example, according to (@PaulSanders)'s request.

Edit 3: Live demos were missing "public:".

Edit 4: Live demo with solution from @Artyer.

Edit 5: Problem solved!


Solution

  • You have a typo: std::conjunction_v<std::is_constructible_v<X, Y>...> should be either std::conjunction_v<std::is_constructible<X, Y>...> or (std::is_constructible_v<X, Y> && ...).

    Your first try doesn't work because enable_if_t<condition, Type&&> is a non-deduced context: The compiler can't guess what enable_if_t<...> is going to be (even if the programmer knows it's going to be Type&&).

    Your second try doesn't work because a parameter pack that isn't the last argument is also a non-deduced context.

    Instead of enable_if_t<condition, bool> dummy = true being the last function argument, make it the last template argument:

        template<typename... NameOrTag, std::enable_if_t<
                std::conjunction_v
                <
                    std::is_constructible<NameAndTag, NameOrTag>...
                >
                , bool> dummy = true>
        PathToObject(NameOrTag&&... obj_path);
    

    This pattern is usually written as std::enable_if_t<(condition)>* = nullptr, being void* = nullptr if the condition is true, or an error otherwise.