Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Allow templates to be inferred

Say I'm using an external package for storing graphs. A BidirectionalGraph takes two templates: a vertex and an edge type:

var graph = new BidirectionalGraph<Vertex, Edge<Vertex>>();

Unfortunately, this graph package doesn't allow you to get the edges radiating into a vertex in a single line. Instead, you have to provide an IEnumerable, which it will populate with the results. This can disrupt a good coding rhythm by making tasks like "loop through all vertices that are successors of vertex x" take far too much code.

I wanted to use .NET's extensions to add a one-line solution to the graph class:

public static class GraphExtensions
{
    public static IEnumerable<TEdge> IncomingEdges<TGraphSubtype, TVertex, TEdge>(this TGraphSubtype graph, TVertex n)
        where TGraphSubtype : BidirectionalGraph<TVertex, TEdge>
        where TEdge : IEdge<TVertex>
    {
        IEnumerable<TEdge> inputEdgesForVertex;
        graph.TryGetInEdges(n, out inputEdgesForVertex);
        return inputEdgesForVertex;
    }
}

But when I call graph.IncomingEdges(vertex), for some reason C# (.NET version 4.5) can't infer the template arguments, so I have to say:

graph.IncomingEdges<GraphThatInheritsFromBidirectionalGraph<VertexType,EdgeType>,VertexType,EdgeType>(vertex). Not really a great improvement.

First, why can't the template types be estimated? I have a feeling it has to do with inheritance, but don't understand. I'm used to using C++, and for some reason feel that gcc could infer the template types.

Second, if this can't be prevented, is the correct design choice to make a graph class for actual use, which inherits from BidirectionalGraph? It seems a waste to have to rewrite the constructors, but I'm sure you'd agree that calling the method with explicit template types is inelegant.

EDIT:

Strangely, the equivalent specification (below) does allow automatic inference of template types. So, even though it solves my initial problem (adding this functionality to the graph), I'd still really like to understand.

public static class GraphExtensions
{
        public static IEnumerable<TEdge> IncomingEdges<TVertex, TEdge>(this BidirectionalGraph<TVertex,TEdge> graph, TVertex n)
            where TEdge : IEdge<TVertex>
        {
            IEnumerable<TEdge> inputEdgesForVertex;
            graph.TryGetInEdges(n, out inputEdgesForVertex);
            return inputEdgesForVertex;
        }
}
like image 957
user Avatar asked Aug 16 '13 11:08

user


People also ask

What is template argument deduction?

Template argument deduction is used in declarations of functions, when deducing the meaning of the auto specifier in the function's return type, from the return statement.

Can a template be a template parameter?

A template argument for a template template parameter is the name of a class template. When the compiler tries to find a template to match the template template argument, it only considers primary class templates. (A primary template is the template that is being specialized.)

Which parameter is allowed for non-type template?

Which parameter is legal for non-type template? Explanation: The following are legal for non-type template parameters:integral or enumeration type, Pointer to object or pointer to function, Reference to object or reference to function, Pointer to member.

What is the purpose of template parameter?

A template parameter is a special kind of parameter that can be used to pass a type as argument: just like regular function parameters can be used to pass values to a function, template parameters allow to pass also types to a function.


1 Answers

The first version of your extension method is able to infer TGraphType and TVertex but not TEgde, as it would require inferring the TEdge from the type constraint:

where TGraphSubtype : BidirectionalGraph<TVertex, TEdge>

which C# compiler does not do (it does not infer generic type parameters from type constraints). I honestly don't know if there is a technical reason behind this or it just wasn't implemented.

Your updated version, on the other hand, includes BidirectionalGraph<TVertex, TEdge> as a parameter, so for example when you call the extension method on a class like:

class AGraph: BidirectionalGraph<AVertex, AnEdge> { ... }
...
var aGraph = new AGraph();
aGraph.IncomingEdges(vertex);

the compiler is able to examine the type AGraph and see that there is a unique type BidirectionalGraph<AVertex, AnEdge> in its inheritance hierarchy, so it is able to infer TVertex and TEdge.

Note that if the parameter type were IGraph<TVertex, TEdge> (instead of BidirectionalGraph<TVertex, TEdge>) and AGraph implemented multiple constructed types of that generic interface, e.g.:

class AGraph: IGraph<AVertex, AnEdge>, 
              IGraph<AnotherVertex, AnotherEdge> { ... }

then type inference would fail once again because it can't tell if, for example, TVertex is AVertex or AnotherVertex.

like image 73
Eren Ersönmez Avatar answered Oct 20 '22 19:10

Eren Ersönmez