Reputation: 4882
(NOTE: ostream is not an option, do not suggest overloading ostream operators as a solution) (NOTE: actual working test code that shows each of the attempts is at the bottom)
Basically I have a class heirachy that looks like this:
template<typename T, typename value_type>
class CRTPBase {
using CRTPBase_value = value_type;
...
}
template<typename T>
class derived_1: public CRTPBase<derived_1, derived_type_1>{
...
}
template<typename T>
class derived_2: public CRTPBase<derived_2, derived_type_2>{
...
}
template<typename T>
class derived_3: public CRTPBase<derived_3, derived_type_3>{
...
}
template<typename T>
class derived_4: public CRTPBase<derived_4, derived_type_4>{
...
}
I want to overload fmt::format for each of the dervied classes, with out duplicating the code for each, as the actual code to print for each type would be exactly the same.
I try first to test with only one of the derived template classes being specialized
template <typename T>
struct fmt::formatter<derived_1<T>>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const derived_1<T>& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, ", ", v(i,j));
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
This works perfectly for
derived_1 d = ...;
fmt::print("{}", d);
Then I try to see if I can somehow use the CRTP base to specialize
template <typename T>
struct fmt::formatter<CRTPBase<T, T::derived_type>>{
template <typename FormatContext>
auto format(const CRTPBase<T, T::derived_type>& v, FormatContext& ctx)
}
This does not work, I get "static assertion failed: don't know how to format the type", there's no valid conversion between the two types, I guess this is because of explicit template conversion issues. I try then
template <typename T>
struct fmt::formatter<CRTPBase<T, T::derived_type>>{
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
}
It appears to skip this, I get "static assertion failed: don't know how to format the type", I guess because what ever mechanism fmt uses internally requires the actual formatter type to be present for this to work.
I try then using SFINAE to see if I can do the same
My is_CRTP_derived class looks something like:
template<class Derived_T>
using derived_element_wise = decltype(Derived_T::CRTPBase_value);
template<class Derived_T>
using is_CRTP_derived = std::experimental::is_detected<derived_element_wise, Derived_T>
The first attempt looks like this:
template <typename T, typename U = enable_if_t<is_CRTP_derived<T>::value>>
struct fmt::formatter<T>{
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
}
I get an error about "template parameters not deducible in partial specialization:" Okay, so that doesn't work Next I try
template <typename T>
struct fmt::formatter<T,enable_if_t<is_CRTP_derived<T>::value>>{
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
}
Seems to completely skip the type, I get I get "static assertion failed: don't know how to format the type". So I try
template <typename T>
struct fmt::formatter<enable_if_t<is_CRTP_derived<T>::value, T>>{
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
}
and I get "error: template parameters not deducible in partial specialization" then I try
template <typename T>
struct fmt::formatter<T>{
using temp = enable_if_t<is_CRTP_derived<T>::value>;
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
}
and I get error: declaration of 'struct fmt::v5::formatter' ambiguates earlier template instantiation for 'struct fmt::v5::formatter'.
And that's were I gave up. I don't know any other way to actually get this to work How do I avoid doing this for each CRTP derived template class?
EDIT:
Here is actual working code that demonstrates the issue:
//main.cpp
#include <fmt/format.h>
#include <fmt/core.h>
#include <experimental/type_traits>
#include <type_traits>
//change 0->6 to see errors of each attempt
#define ATTEMPT 0
template<class Derived_T>
using derived_element_wise = decltype(Derived_T::CRTPBase_value);
template<class Derived_T>
using is_CRTP_derived = std::experimental::is_detected<derived_element_wise, Derived_T>;
template<typename T, typename value_type>
struct CRTPBase {
using CRTPBase_value = value_type;
std::size_t size(){
auto derived = static_cast<const T*>(this);
return derived->width() * derived->height();
}
};
template<typename T>
struct derived_1: public CRTPBase<derived_1<T>, typename std::remove_const<T>::type>{
using derived_type = T;
std::size_t width() const{
return 1;
}
std::size_t height() const{
return 1;
}
};
template<typename T>
struct derived_2: public CRTPBase<derived_2<T>, typename std::remove_const<T>::type>{
using derived_type = T;
std::size_t width() const{
return 2;
}
std::size_t height() const{
return 2;
}
};
template<typename T>
struct derived_3: public CRTPBase<derived_3<T>, typename std::remove_const<T>::type>{
using derived_type = T;
std::size_t width() const{
return 3;
}
std::size_t height() const{
return 3;
}
};
template<typename T>
struct derived_4: public CRTPBase<derived_4<T>, typename std::remove_const<T>::type>{
using derived_type = T;
std::size_t width() const{
return 4;
}
std::size_t height() const{
return 4;
}
};
#if ATTEMPT == 0
// Example properly working printer
template <typename T>
struct fmt::formatter<derived_1<T>>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const derived_1<T>& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
template <typename T>
struct fmt::formatter<derived_2<T>>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const derived_2<T>& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
template <typename T>
struct fmt::formatter<derived_3<T>>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const derived_3<T>& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
template <typename T>
struct fmt::formatter<derived_4<T>>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const derived_4<T>& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
#elif ATTEMPT == 1
template <typename T>
struct fmt::formatter<CRTPBase<T, typename T::derived_type>>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const CRTPBase<T, typename T::derived_type>& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
#elif ATTEMPT == 2
template <typename T>
struct fmt::formatter<CRTPBase<T, typename T::derived_type>>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
#elif ATTEMPT == 3
template <typename T, typename U = std::enable_if_t<is_CRTP_derived<T>::value>>
struct fmt::formatter<T>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
#elif ATTEMPT == 4
template <typename T>
struct fmt::formatter<T,std::enable_if_t<is_CRTP_derived<T>::value>>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
#elif ATTEMPT == 5
template <typename T>
struct fmt::formatter<std::enable_if_t<is_CRTP_derived<T>::value, T>>{
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
#elif ATTEMPT == 6
template <typename T>
struct fmt::formatter<T>{
using temp = std::enable_if_t<is_CRTP_derived<T>::value>;
template <typename ParseContext>
constexpr auto parse(ParseContext& ctx){
return begin(ctx);
}
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx)
{
auto&& out= ctx.out();
if(v.height() > 1){
format_to(out, "[\n");
}
for(std::size_t i = 0; i < v.height(); ++i){
format_to(out, "[");
for(std::size_t j = 0; j < v.width(); ++j){
format_to(out, "{},", 0);
}
format_to(out, "]\n");
}
if(v.height() > 1){
return format_to(out, "]\n");
}
return out;
}
};
#endif
int main(){
derived_1<int> d1;
derived_2<float> d2;
derived_3<bool> d3;
derived_4<double> d4;
fmt::print("{}", d1);
fmt::print("{}", d2);
fmt::print("{}", d3);
fmt::print("{}", d4);
return 0;
}
Upvotes: 2
Views: 809
Reputation: 55595
You can do this with SFINAE:
template <typename T>
struct fmt::formatter<
T, std::enable_if_t<
std::is_base_of_v<CRTPBase<T, typename T::derived_type>, T>, char>> {
auto parse(format_parse_context& ctx) { return ctx.begin();}
template <typename FormatContext>
auto format(const T& v, FormatContext& ctx) {
// Format v and write the output to ctx.out().
return ctx.out();
}
};
Here is a complete working example on Godbolt: https://godbolt.org/z/vsbcc8.
There is also an example of doing this in {fmt} docs: https://fmt.dev/latest/api.html#formatting-user-defined-types.
Upvotes: 3