Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Dedupe some C++ templates #17497

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 26, 2023
Merged

Dedupe some C++ templates #17497

merged 2 commits into from
Jun 26, 2023

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented May 23, 2020

PR Summary

Now that we're messing up with C++ image resampling... ;)

1st commit: Dedupe some C++ templates.
2nd commit: Make sure we don't use C++14 accidentally. (Was originally: don't use C++11 accidentally.)

Edit: apparently running into a MSVC bug (per https://devblogs.microsoft.com/cppblog/expression-sfinae-improvements-in-vs-2015-update-3/); will look into it...

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way

@anntzer anntzer marked this pull request as draft May 23, 2020 14:58
@tacaswell tacaswell added this to the v3.4.0 milestone May 23, 2020
@QuLogic QuLogic modified the milestones: v3.4.0, v3.5.0 Jan 21, 2021
@QuLogic
Copy link
Member

QuLogic commented May 6, 2021

We can use C++11 now, I think; maybe the bug will also be fixed too?

@anntzer
Copy link
Contributor Author

anntzer commented May 6, 2021

We can use C++11 now, I think; maybe the bug will also be fixed too?

Should that be mentioned somewhere in the devdocs?

@QuLogic
Copy link
Member

QuLogic commented May 7, 2021

Probably, I think we just discussed this in Gitter only.

@anntzer
Copy link
Contributor Author

anntzer commented May 7, 2021

OTOH, it looks like MSVC still doesn't like my templates, so perhaps the entire thing is moot...

@tacaswell tacaswell modified the milestones: v3.5.0, v3.6.0 Aug 5, 2021
@timhoffm timhoffm modified the milestones: v3.6.0, unassigned Apr 30, 2022
@story645 story645 modified the milestones: unassigned, needs sorting Oct 6, 2022
@anntzer anntzer marked this pull request as ready for review February 4, 2023 23:14
@anntzer
Copy link
Contributor Author

anntzer commented Feb 4, 2023

Looks like I got this to work again with a healthy dose of std::conditional instead of using std::enable_if...

@jklymak
Copy link
Member

jklymak commented Feb 6, 2023

@anntzer how convinced are we that all these are tested?


using span_gen_t = typename type_mapping_t::template span_gen_nn_type<image_accessor_t, arbitrary_interpolator_t>;
using span_conv_t = agg::span_converter<span_gen_t, span_conv_alpha_t>;
using nn_renderer_t = agg::renderer_scanline_aa<renderer_t, span_alloc_t, span_conv_t>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the three lines that are not tested (as in has line coverage). I guess it would be worthwhile to add a test for this case (nearest interpolation, not affine) outside of this PR?

@anntzer
Copy link
Contributor Author

anntzer commented Feb 7, 2023

I found an even shorter way to write this, which goes on top of #25152, so let's wait for that one first.

@anntzer anntzer marked this pull request as ready for review February 8, 2023 10:08
@anntzer
Copy link
Contributor Author

anntzer commented Feb 8, 2023

OK, now rebased. Let's see what CI says.

@anntzer
Copy link
Contributor Author

anntzer commented Feb 8, 2023

fwiw codecov (which does handle C++ coverage) seems happy now.

@greglucas
Copy link
Contributor

FYI, here is the link to Scipy's docs on C++ version support.
https://docs.scipy.org/doc/scipy/dev/toolchain.html#c-language-standards

So it seems like they are willing to push the bounds even further and go with some C++17 constructs already. We might want to link to that somewhere in the docs if we want to say we support XX version.

@oscargus
Copy link
Member

oscargus commented Feb 8, 2023

FYI, here is the link to Scipy's docs on C++ version support.

Good link! Especially the following:

Since dropping support for Python 2.7, C++11 can be used universally, and since dropping Python 3.6, the Visual Studio version (that had previously been stuck with 14.0 due to ABI compatibility with CPython) has been recent enough to support even C++17.

So I guess the question is what the reasons to pin the C++ numbers that low is. Are there relevant platforms where it is hard (inconvenient) to get a reasonably new C++ compiler?

(type == NPY_INT16) ? resample<agg::rgba16> :
(type == NPY_FLOAT32) ? resample<agg::rgba32> :
(type == NPY_FLOAT64) ? resample<agg::rgba64> :
nullptr)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an obvious benefit to write it is this way?

(In addition to "hide" that there are no tests for short? Which I realize is not the purpose, but a direct consequence. A single raise I can see, but other than that, the previous code was easier to read for me anyway, But then I know that you like these constructs in general (and I don't...), so I can ignore that if there is a benefit.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me the point of writing it like this is to allow focusing on what matters (the NPY_FOO -> agg:type mapping, each pair of which now appears on a single line), and reduce line noise (input/output/params, which are the same every time, and the breaks; which add no info).

The fact that this "hides" that a branch is not taken is not a plus (if anything it's a minus), we should enable branch coverage in C++ too if possible.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rather like it in this form in this particular case. I think it is quite readable. Did the code here get updated in the meantime as I don't see anything getting hidden?

@anntzer
Copy link
Contributor Author

anntzer commented Feb 8, 2023

I would be happy to allow a more recent C++ standard but I don't feel particularly strongly about it; the point here was more to make sure we don't go beyond whatever we agree on.

@jklymak jklymak added the status: needs comment/discussion needs consensus on next step label Feb 11, 2023
@tacaswell
Copy link
Member

We do document that we currently require c++11 at https://matplotlib.org/stable/devel/dependencies.html#c-compiler

Matplotlib requires a C++ compiler that supports C++11.

along with which versions of major compilers (gcc, clang, visual studio).

@efiring
Copy link
Member

efiring commented Jun 5, 2023

It looks like this is ready to go, pending review by a C++ wizard, which rules me out. If I understand correctly, the current documentation that we require C++11 is adequate for this PR; I don't see anything in the comments indicating that more discussion is really needed.

@efiring efiring removed the status: needs comment/discussion needs consensus on next step label Jun 5, 2023
@efiring efiring modified the milestones: future releases, v3.8.0 Jun 5, 2023
@tacaswell
Copy link
Member

@ianthomas23 willing to look at this?

Copy link
Member

@ianthomas23 ianthomas23 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks OK to me apart from nitpicking typo.

@@ -500,240 +500,58 @@ typedef enum {
} interpolation_e;


template <typename T>
class type_mapping;
// T is rgba iff it has an T::r field.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// T is rgba iff it has an T::r field.
// T is rgba if it has an T::r field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this iff = if and only if?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evidently that wasn't clear to me.

Maybe it should be written out in full? The templated code is essentially obfuscated, so maybe the English should try to go in the other direction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there more that can be done to clarify the code, via some combination of comments and changes in the code itself?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not really. Clever templated C++ is usually difficult to understand unless you are seeing it regularly.

This is a significant reduction in the number of lines of code. Each line is more difficult to understand, but the reduction in the number of lines means it is probably easier to understand overall.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evidently that wasn't clear to me.

Good point.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, wrote out "if and only if".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ianthomas23 Do you think writing type_mapping as

template<typename color_type, typename Enable = void>
struct type_mapping {};


template<typename color_type>
struct type_mapping<color_type, typename std::enable_if<!is_grayscale<color_type>::value>::type>
{
    using blender_type = typename std::conditional<
        std::is_same<color_type, agg::rgba8>::value,
        fixed_blender_rgba_plain<color_type, agg::order_rgba>,
        agg::blender_rgba_plain<color_type, agg::order_rgba>
    >::type;
    using pixfmt_type =
        agg::pixfmt_alpha_blend_rgba<blender_type, agg::rendering_buffer>;
    using pixfmt_pre_type = typename
        agg::pixfmt_alpha_blend_rgba<
            typename std::conditional<
                std::is_same<color_type, agg::rgba8>::value,
                fixed_blender_rgba_pre<color_type, agg::order_rgba>,
                agg::blender_rgba_pre<color_type, agg::order_rgba>
            >::type,
            agg::rendering_buffer>;
    template<typename A> using span_gen_affine_type =
        agg::span_image_resample_rgba_affine<A>;
    template<typename A, typename B> using span_gen_filter_type =
        agg::span_image_filter_rgba<A, B>;
    template<typename A, typename B> using span_gen_nn_type =
        agg::span_image_filter_rgba_nn<A, B>;
};


template<typename color_type>
struct type_mapping<color_type, typename std::enable_if<is_grayscale<color_type>::value>::type>
{
    using blender_type = agg::blender_gray<color_type>;
    using pixfmt_type = agg::pixfmt_alpha_blend_gray<blender_type, agg::rendering_buffer>;
    using pixfmt_pre_type = pixfmt_type;
    template<typename A> using span_gen_affine_type =
        agg::span_image_resample_gray_affine<A>;
    template<typename A, typename B> using span_gen_filter_type =
        agg::span_image_filter_gray<A, B>;
    template<typename A, typename B> using span_gen_nn_type =
        agg::span_image_filter_gray_nn<A, B>;
};

(essentially getting rid of most std::conditional in favor of partial template specialization) reads better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very slightly. But as there is already use of std::conditional maybe it is better to keep all of them for consistency. On balance, I'd be inclined to keep it as it is (commit 9644a9c).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Note that I could also split out the agg::rgba8 case as another template specialization (duplicating some of the rgba type_mapping) to fully get rid of std::conditional, if you prefer.)

@anntzer anntzer force-pushed the enable_if branch 2 times, most recently from 3314f4b to 9644a9c Compare June 13, 2023 08:51
@oscargus
Copy link
Member

Do we have another C++-guru to review this or should someone just merge it?

@WeatherGod
Copy link
Member

WeatherGod commented Jun 14, 2023 via email

Copy link
Member

@WeatherGod WeatherGod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I didn't have time to verify that the deduplication was done exactly correctly, I think this is quite readable. The original code probably should have had some narrative comment explaining the reason and/or purpose of the different template classes, and at least now I can better see what the differences are and get a sense of the reasons. It probably wouldn't hurt to add a short explanation of what is going on, though.

const T *input, int in_width, int in_height,
T *output, int out_width, int out_height,
const void *input, int in_width, int in_height,
void *output, int out_width, int out_height,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why change to void? Why not color_type?

Copy link
Contributor Author

@anntzer anntzer Jun 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so that the big switch below ((type == NPY_UINT8) ? resample<agg::gray8> : ...) works: as things are written currently, all template instantiations of resample have the same (function pointer) type (*void)(const void*, int, ...). If I explicitly put color_type instead, then they would have different types (the first argument would have different types) and the ternary would not work.

re: the ternary below: no, there was no other update I think.

(type == NPY_INT16) ? resample<agg::rgba16> :
(type == NPY_FLOAT32) ? resample<agg::rgba32> :
(type == NPY_FLOAT64) ? resample<agg::rgba64> :
nullptr)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rather like it in this form in this particular case. I think it is quite readable. Did the code here get updated in the meantime as I don't see anything getting hidden?

@oscargus
Copy link
Member

@WeatherGod thanks! No hurry really. More that I didn't want to wait for someone that didn't exist...

@anntzer
Copy link
Contributor Author

anntzer commented Jun 16, 2023

The original code probably should have had some narrative comment explaining the reason and/or purpose of the different template classes, and at least now I can better see what the differences are and get a sense of the reasons. It probably wouldn't hurt to add a short explanation of what is going on, though.

I think Agg, to a large extent, is architected around chaining templates like it is done in resample() (like it or not); all type_mapping is doing is providing the types to fill in these templates.

Copy link
Member

@WeatherGod WeatherGod left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That explanation makes sense to me. I'm fine with this.

@WeatherGod WeatherGod merged commit e528649 into matplotlib:main Jun 26, 2023
@WeatherGod
Copy link
Member

Failure seems unrelated?

FAILED lib/matplotlib/tests/test_backends_interactive.py::test_interactive_backend[toolbar2-{'MPLBACKEND': 'macosx'}] - subprocess.TimeoutExpired: Command '['/Users/runner/hostedtoolcache/Python/3.9.17/x64/bin/python', '-c', 'from matplotlib.tests.test_backends_interactive import _test_interactive_impl; _test_interactive_impl()', '{"toolbar": "toolbar2"}']' timed out after 120 seconds

@anntzer anntzer deleted the enable_if branch June 26, 2023 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.