Home > Back-end >  Problem with std::derived_from giving reference type on dereferenced iterator
Problem with std::derived_from giving reference type on dereferenced iterator

Time:06-14

There's going to be a lot of code in this post, so I want to apologize for that right off the bat. Code that's not related to the problem and most function documentation has been removed, so feel free to ask for it if it becomes relevant.

I'm trying to make a print essentially acts as a "dispatch" to a println function, which has multiple overloads for different data types. In my code, everything is in a cu namespace, hence why it exists in my question. Here's some code to help understand how this system works. My problem is specific to the cu::ptext class when trying to print a std::vector<cu::ptext> object.

FILE: println.h

// All needed includes

namespace cu
{
    template<typename Dp>
    concept DefaultPrintable = requires(std::ostream& os, Dp ln)
    {
        os << ln;
    };
}

#ifdef _WIN32

namespace cu
{
    template<typename Po>
    concept PrettyTextObject = std::derived_from<Po, console::ptext> && requires(Po text)
    {
        text.display();
    };

    template<typename Ip>
    concept IteratorPrintable = !DefaultPrintable<Ip> && requires(Ip container)
    {
        std::begin(container);
        std::end(container);
        requires DefaultPrintable<decltype(*std::begin(container))> || 
            PrettyTextObject<decltype(*std::begin(container))>;
    };
}

#endif

namespace cu
{
    template<DefaultPrintable Dp>
    void println(Dp line)
    {
        std::cout << line;
    }
}

#ifdef _WIN32

namespace cu
{
    template<PrettyTextObject Po>
    void println(const Po& pretty_text)
    {
        pretty_text.display();
    }
}

#endif

namespace cu
{
    template<IteratorPrintable Ip>
    /**
     Allows iteratiable objects with well-formed std::begin() and std::end() to
     be printed, with each element being printed on its own line. THIS FUNCTION
     SHOULD NOT BE ACCESSED DIRECTLY. Instead, use cu::print() or cu::prompt().

     @param container The container whose elements should be printed.
    */
    void println(Ip container)
    {
        // Figure out size of container
        size_t elements_ct = 0;
        for (auto el = std::begin(container); el != std::end(container);   el)
            elements_ct  ;

        if (elements_ct > 0)
        {
            using ItTy = decltype(std::begin(container));

            struct ContainerLoop { ItTy itr; size_t i; };
            for (ContainerLoop cl{ std::begin(container), 0 }; cl.itr != std::end(container);   cl.itr)
            {
                println(*cl.itr);
                if (cl.i   < elements_ct - 1)
                    std::cout << '\n';
            }
        }
    }
}

namespace cu
{
    template<typename Ty>
    concept Printable = requires(Ty ln)
    {
        println(ln);
    };
}

FILE: print.h

namespace cu
{
    /**
     Null case for print() which flushes the stream.
    */
    inline void print()
    {
        std::cout << std::flush;
    }

    template<Printable Pt, Printable... Pts>
    /**
     Prints all arguments to console on their own line. All arguments must be
     well-defined for cu::println([arg]).

     @param line   Current object to print to console
     @param others All other objects to send through recursive function call
    */
    void print(Pt line, Pts... others)
    {
        println(line);
        std::cout << '\n';

        print(std::forward<Pts>(others)...);
    }
}

DRIVER CODE

cu::print("This is a test");  // <-- This line compiles fine 

std::vector<std::string> stest{ "This", "is", "test" };
cu::print(stest); // <-- Also compiles fine

cu::print(cu::ptext("This is a test")); // <-- Compiles as well

std::vector<cu::ptext> ptest{ "This", "is", "test" };
cu::print(ptest);  // <-- This line does not compile

To reproduce this error: https://godbolt.org/z/KroK4YehG
(Which has the following compiler output)

<source>:151:1: error: no matching function for call to 'print'
cu::print(ptest);  // <-- This line does not compile
^~~~~~~~~
<source>:133:10: note: candidate template ignored: constraints not satisfied [with Pt = std::vector<cu::ptext>, Pts = <>]
    void print(Pt line, Pts... others)
         ^
<source>:125:14: note: because 'std::vector<cu::ptext>' does not satisfy 'Printable'
    template<Printable Pt, Printable... Pts>
             ^
<source>:111:9: note: because 'println(ln)' would be invalid: no matching function for call to 'println'
        println(ln);
        ^
<source>:120:17: note: candidate function not viable: requires 0 arguments, but 1 was provided
    inline void print()
                ^
1 error generated.
Compiler returned: 1

CodePudding user response:

The problem is here

template<typename Ip>
concept IteratorPrintable = !DefaultPrintable<Ip> && requires(Ip container) {
  std::begin(container);
  std::end(container);
  requires DefaultPrintable<decltype(*std::begin(container))> || 
           PrettyTextObject<decltype(*std::begin(container))>;
 };

decltype(*std::begin(container)) gives you a reference type, which makes std::derived_from in PrettyTextObject fail.

The workaround is to use remove_reference to remove the reference, or more simply, just use ranges::range_value_t, which already constrains Ip to be a range.

template<typename Ip>
concept IteratorPrintable = !DefaultPrintable<Ip> && 
  (DefaultPrintable<std::ranges::range_value_t<Ip>> || 
   PrettyTextObject<std::ranges::range_value_t<Ip>>);
  • Related