Search code examples
c++templatesc++17incomplete-typeclass-template

Constraining parameter type in derived class to fit capabilities of base class


I have a type variant2 derived from std::variant. Now I want to be able to early capture potential assignment errors in case the assigned type doesn't match one of the variants types.

However, the way I came up with suffers from the issue that the typedef of the inner std::variant is incomplete until the closing braces, so I can't pass that through a concept. What alternatives do I have?

(Note: In production code the function assign is a lot more complicated, hence I want to be able to capture errors for the sake of user-friendliness early.)

Demo

#include <concepts>
#include <iostream>
#include <string>
#include <type_traits>
#include <variant>

template <typename T, typename Variant>
struct is_variant_type;

template <typename T, template <typename...> typename Var, typename... Args>
struct is_variant_type<T, Var<Args...>>
    : public std::disjunction<std::is_same<T, Args>...> {};

template <typename T, typename Variant>
concept variant_type = is_variant_type<T, Variant>::value;

template <typename T>
struct variant2 : public std::variant<std::monostate, int, T> 
{
    using inner_variant_type = variant2::variant;

    template <variant_type<inner_variant_type> U>
    auto assign(const U& arg) 
    {
        *this = arg;
    }
};

int main() 
{
    variant2<std::string> var;
    var.assign(2);
}

Yields:

<source>:19:42: error: invalid use of incomplete type 'struct variant2<T>'
   19 |     using inner_variant_type = variant2::variant;
      |                                          ^~~~~~~
<source>:18:8: note: definition of 'struct variant2<T>' is not complete until the closing brace
   18 | struct variant2 : public std::variant<std::monostate, int, T> {
      |        ^~~~~~~~
<source>:21:28: error: 'inner_variant_type' was not declared in this scope; did you mean 'is_variant_type'?
   21 |     template <variant_type<inner_variant_type> U>
      |                            ^~~~~~~~~~~~~~~~~~
      |                            is_variant_type

etc.


Solution

  • You are missing a typename keyword, for the dependent type variant2::variant, in the alias declaration inside the class variant2.

    Additionally, you have to mention which operator=, that you meant at the line *this = var;, which is by default not visible to the child. You can have more read here: Inheritance and templates in C++ - why are inherited members invisible?

    template <typename T>
    struct variant2 : public std::variant<std::monostate, int, T> 
    {
        using inner_variant_type = typename variant2::variant;
        //                         ^^^^^^^^ ---> required 
    
        // required to mention, that the operator= is from the parent !!
        using std::variant<std::monostate, int, T>::operator=;  
    
        template <variant_type<inner_variant_type> U>
        auto assign(const U& var) {
            *this = var;
        }
    };
    

    See a demo