Gardener
Gardener

Reputation: 2660

How can I use is_invocable() to create a template for overloading << for printing out an object?

I appreciated the answers to this stackoverflow question Generic implementation of operator<< function in terms of the dump member function

However, the answer by Richard Hodges suggests that C++17's is_invocable() can be used to simplify this solution.

When I try to implement a solution using is_invocable(), I get a compile error.

I have an #ifdef 0 around the code that works without the is_invocable call. That works.

How do I need to change my code to get the is_invocable call to work?

Thank you!

Compile error:

main.cpp:16:46: error: reference to non-static member function must be called
  static_assert(std::is_invocable<decltype(t.dump),
                                           ~~^~~~
main.cpp:47:13: note: in instantiation of function template specialization 'operator<<<Foobar, char>' requested here
  std::cout << foobar << std::endl;
            ^
main.cpp:32:32: note: possible target for call
    std::basic_ostream<charT> &dump(std::basic_ostream<charT> &o) const

Code:

#include <iostream>
#include <string>
#include <type_traits>

template<typename T, typename charT>
auto operator<<(std::basic_ostream<charT> &str, const T &t) -> decltype(t
    .dump(str))
{
#if 0
  static_assert(std::is_same
                    <decltype(t.dump(str)),
                std::basic_ostream<charT> &>::value,
                ".dump(ostream&) does not return ostream& !");

#else
  static_assert(std::is_invocable<decltype(t.dump),
                std::basic_ostream<charT> &>::value,
                ".dump(ostream&) does not return ostream& !");
#endif

  return t.dump(str);
}

class Foobar {
public:
    Foobar(std::string nameArg)
        :
        name(nameArg)
    {}

    template<typename charT>
    std::basic_ostream<charT> &dump(std::basic_ostream<charT> &o) const
    {
      return (o << name);
    }

private:
    std::string name;

};

int main()
{
  Foobar foobar("private name");

  std::cout << foobar << std::endl;
  return 0;
}

Based on the answer by @Oktalist, I have modified my code as follows. I leave the original code so the question makes sense. The modified code does not compile. I am sure I am just missing a nuance of how this works.

The compile error is

error: invalid operands to binary expression ('std::__1::ostream' (aka 'basic_ostream<char>') and 'Foobar') std::cout << foobar << std::endl;

Here is the updated code that does not compile:

#include <iostream>
#include <string>
#include <type_traits>

    template<typename T, typename charT>
auto operator<<(std::basic_ostream<charT> &str, const T &t) ->
  decltype(t.dump)
{
  static_assert(std::is_invocable_r<
      std::basic_ostream<charT> &,  // return type
      decltype(&T::dump),           // invocable type
      T const &,                    // first arg type (implicit this)
      std::basic_ostream<charT> &   // second arg type
    >::value, "dump(ostream&) does not return ostream& !");
  return t.dump(str);
}

class Foobar {
public:
    Foobar(std::string nameArg)
        :
        name(nameArg)
    {}

    template<typename charT>
    std::basic_ostream<charT> &dump(std::basic_ostream<charT> &o) const
    {
      return (o << name);
    }

private:
    std::string name;

};

int main()
{

  Foobar foobar("private name");

  std::cout << foobar << std::endl;
  return 0;
}

Upvotes: 0

Views: 772

Answers (1)

Oktalist
Oktalist

Reputation: 14714

First of all, decltype(t.dump) is invalid. You want decltype(&T::dump).

Secondly, you need std::is_invocable_r if you want to check the return type, and you need to supply both argument types too:

static_assert(std::is_invocable_r<

                  std::basic_ostream<charT> &,  // return type
                  decltype(&T::dump),           // invocable type
                  T const &,                    // first arg type (implicit this)
                  std::basic_ostream<charT> &   // second arg type

              >::value, ".dump(ostream&) does not return ostream& !");

The above won't work as-is in your case, as dump is a function template.

std::is_invocable_r<

    std::basic_ostream<charT> &,
    decltype(&T::template dump<charT>),
    T const &,
    std::basic_ostream<charT> &

>::value

That would work, but you are limiting yourself to a certain signature for dump and I'm left wondering why you think it is helpful to use is_invocable here at all.

Upvotes: 1

Related Questions